Skip to content

Fixable errors v2#19

Merged
cuzzo merged 12 commits intomasterfrom
fixable-errors-v2
May 8, 2026
Merged

Fixable errors v2#19
cuzzo merged 12 commits intomasterfrom
fixable-errors-v2

Conversation

@cuzzo
Copy link
Copy Markdown
Owner

@cuzzo cuzzo commented May 8, 2026

No description provided.

cuzzo and others added 12 commits May 8, 2026 02:01
Adds nine new auto-fixes surfaced via the FixableFinding pipeline so
`clear fix` (and downstream LSP code-action support) can resolve
common errors without the user reaching for the keyboard.

Tier 1 (4 fixes — high-confidence single-edit):
- IMMUTABLE_ASSIGNMENT: insert MUTABLE at the binding declaration.
- IMMUTABLE_ARG_PASSED_AS_MUTABLE: same, at the caller's binding.
- CAPTURE_IMMUTABLE_AS_MUTABLE: same, for USE(MUTABLE x) captures.
- AMBIGUOUS_RETURN: insert RETURNS :Any before the function arrow.

Tier 2 (5 fixes — multi-edit / refactor):
- MATCH PARTIAL fixes for non-discriminated / non-exhaustive subjects.
- RETURN_BORROWED_NO_COPY: wrap RETURN value with COPY.
- @Local capability lints: remove redundant @Local.
- @shared:atomic escape: migrate to @shared:locked (interactive
  confidence — user reviews before accepting).
- M2.1 operator-aware Auto type ranking on gradual-typing inference.

All fixes route through the shared Fix / Edit / Span data model
(spec/fixable_t1_spec.rb, spec/clear_fix_spec.rb).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an MVP LSP for CLEAR sourced from the existing compiler pipeline
(Lexer -> Parser -> SemanticAnnotator + FixCollector). Editors get
diagnostics, hover, and code actions over a debounced incremental
sync — no rewrite of compiler internals needed.

Server (src/lsp/, bin/clear-lsp):
- JSON-RPC framing + server lifecycle (initialize / shutdown / exit).
- DocumentStore + analyzer + textDocument/publishDiagnostics on
  didOpen, didChange, didClose.
- Per-URI debounce (default 100ms) with thread-safe stdout writes.
- textDocument/codeAction surfaces FixableFinding fixes as quickfix /
  refactor entries with isPreferred when confidence is :auto.
- textDocument/hover renders DiagnosticRegistry markdown (cause + fix
  hint + spec example), with same-line fallback when the cursor
  misses the diagnostic's narrow anchor span.
- End-to-end integration spec drives bin/clear-lsp under bundle exec.

Editor integration:
- Neovim: built-in LSP client config + LspAttach keymaps + autocmd
  bootstrap, documented in src/lsp/README.md.
- VS Code extension (.vscode/extensions/cheat-lang/): TypeScript
  client with vscode-languageclient stdio transport, auto-detects
  bin/clear-lsp by walking up from the install path, spawns under
  bundle exec by default.
- tmLanguage / Vim syntax brought up to date with current CLEAR
  (-- -> # comments, |> pipe, every keyword/sigil through v0.2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds .devcontainer/ so opening the repo in Codespaces (or any
Dev-Containers-aware editor) gets a working CLEAR development
environment without manual setup steps.

setup.sh (postCreateCommand):
- bundle install for compiler deps.
- Pinned Zig 0.16.0 toolchain matching CI.
- Builds the in-repo VS Code extension (npm install + tsc).
- Symlinks the built extension into ~/.vscode-server/extensions/
  using the <publisher>.<name>-<version> naming convention VS Code
  Server expects (the .vscode/extensions/ workspace folder is a
  Cursor-only convention; vanilla VS Code Server ignores it).

devcontainer.json:
- Ruby 3.2 base image, Node 22 + git features.
- File associations + recommended marketplace extensions.
- postStartCommand re-runs `bundle install --quiet` on every boot
  so the LSP self-heals if the gem cache went missing between
  sessions (idempotent, near-noop when gems are already on disk).

bin/clear-lsp:
- Accepts --stdio as a no-op (vscode-languageclient appends it by
  default for stdio transport; rejecting it crashed the LSP five
  times before VS Code disabled it).
- Same defensive treatment for --node-ipc / --pipe / --socket=...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a function body reassigns / index-assigns / field-assigns a
parameter the caller declared without MUTABLE, the three errors that
fire (ASSIGN_VAR_IMMUTABLE, ASSIGN_INDEX_IMMUTABLE_LIST,
ASSIGN_FIELD_IMMUTABLE_STRUCT) now emit a FixableFinding whose :auto
fix inserts `MUTABLE ` at the parameter's column in the signature.

The same `build_declare_mutable_fix` builder used for local-binding
fixes now falls back to the param's name token (captured at parse
time and stashed on the SymbolEntry as `param_decl_token`) when the
binding has no `reg` of its own — params don't carry a VarDecl.

Verified end-to-end via `clear fix`: a `parseValue!(penv: HashMap...)`
that index-assigns into `penv` rewrites to `MUTABLE penv: HashMap...`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When `p.zz` is accessed on a struct whose schema doesn't declare
'zz', visit_GetField now routes through emit_typo_suggestion! with
the struct's actual fields as the candidate set. If a field is
within Levenshtein threshold ('zz' -> 'x' or 'y' on Point), the
diagnostic carries an :auto fix that replaces the typed token with
the closest field name.

Falls back to the bare ILLEGAL_FIELD_LOOKUP / TYPO_SUGGESTION_REJECTED
when no candidate is close enough, so users without typos still get
the same shape of error they had before.

Two existing specs updated their assertions to match the new clearer
error message ("Struct 'X' has no field 'y'" instead of the old
"Cannot determine struct type for field access").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A MATCH struct pattern that names a non-existent field now offers a
typo-suggestion auto-fix when the closest declared field is within
Levenshtein threshold. Covers three pattern shapes — all share the
single parser change that captures the field name token in
parse_struct_pattern as f[:name_token]:

- value-match form `{ xs: 1 }` on STRUCT P { x, y } -> suggest 'x'
  (annotate_struct_pattern! site, MATCH_FIELD_UNKNOWN).
- destructure form `{ xx }` on STRUCT P { x, y } -> suggest 'x'
  (same site, same code path).
- union variant destructure `Shape.Circle { radiu }` on
  Circle { radius: Float64 } -> suggest 'radius'
  (visit_MatchStatement variant branch, MATCH_DESTRUCTURE_FIELD_UNKNOWN).

Falls back to the bare error code when no candidate is close enough.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A function that calls itself directly (or via a transitive cycle)
without an explicit reentrance declaration used to raise one of three
errors with no fix:

- REENTRANCE_DIRECT_RECURSIVE: directly recursive + @nonReentrant.
- REENTRANCE_INDIRECT_RECURSIVE: directly recursive + no marker.
- REENTRANCY_MUTUAL_CYCLE: transitive cycle + no marker.

All three now emit a FixableFinding whose :auto fix inserts
`EFFECTS REENTRANT ` immediately before the function's `->`, using
the FunctionDef's `arrow_token` as the insertion anchor.

Verified end-to-end via `clear fix`: a `factorial(n)` that calls
itself rewrites the signature to
`FN factorial(n: Int64) RETURNS Int64 EFFECTS REENTRANT ->` and
compiles cleanly.

Two existing capabilities specs had their assertions updated to
reflect the new error wording (`Replace @nonReentrant with EFFECTS
REENTRANT` and `EFFECTS REENTRANT` instead of the old `@reentrant`
phrasing). Audit budget for fixable_helpers.rb bumped from 1 to 2
to cover the second `code:`-variable wrapper helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three typo-suggestion fixes covering parser-side capability errors:

- UNKNOWN_CAPABILITY_SIGIL (chained form):
    `@multiowned:lokced` -> `@multiowned:locked`
    Candidate set is CAP_SIGIL_ATTRS.keys (with/without @ depending
    on whether the user typed it). The chain syntax accepts both.

- UNKNOWN_CAPABILITY_MODIFIER (chained form, same site):
    Wired through the same chain handler. Bare-form typos
    (`@sharred` directly) die in expression parsing earlier and
    aren't reachable from this code path; the chained form is the
    actually-firing case.

- UNKNOWN_WITH_CAPABILITY:
    `WITH RESTRIKT x { ... }` -> `WITH RESTRICT x { ... }`.
    Detection runs both as a pre-loop TYPE_ID check (catches
    uppercase typos that tokenize as TYPE_ID, missing the KEYWORD
    branch) and inside the loop for the rare KEYWORD-but-not-in-
    AST::CAPABILITIES case.

Plumbing: `Parser` now `include FixableHelper` so the parser can
reach `emit_typo_suggestion!` and the underlying Levenshtein
helpers — no annotator state is touched by these utilities.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five new parser-side typo suggestions:

- UNKNOWN_EFFECT (`:saf` -> `:safe`): EXTERN FN EFFECTS clause.
- UNKNOWN_ALLOC_QUALIFIER (`:alloc:frme` -> `:alloc:frame`): the
  qualifier after `:alloc` (frame|heap).
- UNKNOWN_FN_EFFECT (`EFFECTS RENTRANT` -> `EFFECTS REENTRANT`):
  the function-level EFFECTS clause.
- UNKNOWN_REQUIRES_FAMILY (`REQUIRES p: LOKKED` -> `LOCKED`):
  candidate set is the union of REQUIRES_VALID_FAMILIES and
  REQUIRES_REENTRANCE_KINDS so either category is suggested.
- UNKNOWN_REENTRANT_VARIANT (`EFFECTS REENTRANT:THONK` ->
  `EFFECTS REENTRANT:THUNK`): the four valid variants.

Four existing parser specs updated their assertions to match the
new clearer error wording (e.g., "Unknown reentrant variant 'X'"
instead of "Unknown REENTRANT variant ':X'").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four more typo-suggestion fixes:

- UNKNOWN_BG_PREFIX: `BG { @srvice -> ... }` -> `@service`.
  Candidate set = BG_SIGILS keys (with/without `@` based on input).
  Loop entry condition relaxed to admit `@<typo>` so the typo
  branch fires instead of silently falling through.

- UNKNOWN_BRANCH_PREFIX: same shape for DO branches via
  DO_BRANCH_SIGILS.

- UNKNOWN_STRUCT_TYPE: `Pont{x: 1}` -> `Point` when a struct named
  Point is in scope. Candidate set = `all_known_type_names` (a new
  ScopeHelper method that aggregates `@types` keys across the
  scope stack).

- UNKNOWN_TYPE: same `all_known_type_names` candidate set, fired
  from generic_analysis.rb when `Pir<Int64>` is unresolved and
  `Pair` is in scope.

Skipped: UNKNOWN_LITERAL is defensive (unreachable user-typo path),
UNION_METHOD_MISSING is about missing implementations not typos,
UNION_INLINE_VARIANT_UNKNOWN_FIELD needs per-field token plumbing
in the parser (deferred).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`Unknown reentrant variant 'X'` -> `Unknown REENTRANT variant 'X'`.
REENTRANT is a CLEAR keyword (used in `EFFECTS REENTRANT[:VARIANT]`);
keeping it lowercase in error prose obscures that the user is
referring to a syntactic token, not the English adjective. Matches
how other error messages already handle WITH, REQUIRES, BG, etc.

Two thunk parser specs updated their regex assertions to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per WALKTHROUGH.md §4, ownership-violation errors now lead with
"USE AFTER MOVE:", quote the variable in backticks, and explain
*how* the value was consumed using past-tense CLEAR keywords (GAVE,
TOOK, RETURNED, SHARED, MOVED) instead of opaque "moved at line X
by Y" mechanical phrasing.

Two voices, one rule (matches reader expectations):

- Active form (USE_OF_MOVED_VALUE / USE_OF_MOVED_IN_LOOP) — the
  consumer is the subject:
    USE AFTER MOVE: You can't use `msg`. `process(GIVE msg)`
    already GAVE it away (line 7).
    USE AFTER MOVE: You can't use `msg`. `process(msg)` already
    TOOK it away (line 7).

- Passive form (USE_OF_MOVED_PATH) — the owner of the path is the
  subject:
    USE AFTER MOVE: You can't use `o.inner`. Its owner `o` was
    already MOVED on line 6.

Loop body coda matches WALKTHROUGH:
    USE AFTER MOVE: You can't use `v` here. Values can only be
    TAKEN once; subsequent iterations have nothing left to GIVE.

Implementation:

- Diagnostic templates switch to `%{message}` and the helper
  builds the full message; metadata (summary / cause / fix_hint)
  rewritten to match.
- New helpers `emit_use_of_moved_in_loop_error!` and
  `emit_use_of_moved_path_error!` join the existing
  `emit_use_of_moved_error!`. Each picks active vs passive verb
  via the new `OWNERSHIP_ACTIVE_PHRASES` /
  `OWNERSHIP_PASSIVE_PHRASES` tables.
- `consumer_source_text` extracts the move-site source line so
  the error can quote the consumer call ("process(GIVE msg)").
  This requires `@source_code` — `compile` now threads `cheat_code`
  into `SemanticAnnotator.new`.
- `move_if_not_copyable!` now respects an existing more-specific
  move action (was overwriting `:give` from visit_GiveNode with
  `:move`); the function-call site explicitly passes `:takes` /
  `:give` so the verb maps to "TOOK" / "GAVE".

Spec churn: ~30 assertions across 8 files updated to match new
wording. The audit budget for fixable_helpers.rb bumped from 2 to
3 to cover the new `code:`-variable wrapper helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

🐰 Bencher Report

Branchfixable-errors-v2
Testbedubuntu-latest

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
Benchmarkleak-build-msMeasure (units) x 1e3leak-countMeasure (units)leak-run-msMeasure (units)
benchmarks/concurrent/05_backpressure/bench📈 view plot
⚠️ NO THRESHOLD
5.06 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
1,772.41 units
benchmarks/concurrent/10_shard_vs_locked/bench📈 view plot
⚠️ NO THRESHOLD
5.05 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
60,004.73 units
benchmarks/concurrent/16_observables/bench📈 view plot
⚠️ NO THRESHOLD
4.89 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
95.47 units
benchmarks/inter-clear/03_concurrent_mvcc_vs_rwlock/bench📈 view plot
⚠️ NO THRESHOLD
5.28 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
321.73 units
benchmarks/sequential/07_pointer_chase/bench📈 view plot
⚠️ NO THRESHOLD
4.83 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
429.37 units
benchmarks/sequential/12_weak_ref_graph/bench📈 view plot
⚠️ NO THRESHOLD
4.85 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
127.29 units
benchmarks/server/03_pathological/server📈 view plot
⚠️ NO THRESHOLD
4.99 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
1,002.68 units
🐰 View full continuous benchmarking report in Bencher

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 8, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 96.75037% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.85%. Comparing base (0b6c26b) to head (ae4cca0).

Files with missing lines Patch % Lines
src/annotator.rb 79.48% 8 Missing ⚠️
src/annotator-helpers/fixable_helpers.rb 95.08% 6 Missing ⚠️
src/annotator-helpers/generic_analysis.rb 0.00% 4 Missing ⚠️
src/lsp/hover.rb 97.26% 2 Missing ⚠️
src/annotator-helpers/effects.rb 80.00% 1 Missing ⚠️
src/ast/parser.rb 98.07% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #19      +/-   ##
==========================================
+ Coverage   89.73%   89.85%   +0.11%     
==========================================
  Files         173      182       +9     
  Lines       46723    47332     +609     
  Branches    11604    11766     +162     
==========================================
+ Hits        41927    42529     +602     
- Misses       4796     4803       +7     
Flag Coverage Δ
ruby 85.98% <96.75%> (+0.27%) ⬆️
zig 95.59% <ø> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cuzzo cuzzo merged commit d92080e into master May 8, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants