Skip to content

feat(lint): E003 scope-precise binding sets + did-you-mean (#404 increment two)#470

Merged
InauguralPhysicist merged 1 commit into
mainfrom
feat-404-scoped-e003
Jul 7, 2026
Merged

feat(lint): E003 scope-precise binding sets + did-you-mean (#404 increment two)#470
InauguralPhysicist merged 1 commit into
mainfrom
feat-404-scoped-e003

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

Part of #404 (issue stays open — path-precise analysis and token-precise LSP ranges remain).

What

E003's binding sets go from whole-file over-approximation to scope-precise (still path-insensitive), and undefined-name messages gain an edit-distance-1 did-you-mean suggestion.

New true-positive classes — each one is a program the runtime rejects with undefined variable:

  • sibling/module reads of a function-local (fresh-name is or local)
  • module calls of a nested define (enclosing-local by the runtime's rules)
  • post-loop reads of a module-level for variable (the VM loop-scopes it — the same divergence class ouroboros PR Update self-hosting quickstart to reflect bootstrap success #79 hit; function-level for-vars survive, and the lint matches)

Method

Every scope rule was pinned empirically against the interpreter first (10 probe programs), then implemented: closures read enclosing scopes, module names are order-insensitive, listcomp vars leak to the containing scope, catch vars bind in the containing scope. The scope chain literally is the runtime's Env parent chain; COLLECT creates one Env per scope-introducing node in walk order and FLAG replays them by counter — the two walks visit identical nodes in identical order by construction.

External contributions (load_file targets, # lint: loaded-by composers) deliberately stay a flat over-approximation: scope-narrowing someone else's file buys precision nobody asked for at real false-positive risk.

Suggestions are distance-1 only, names ≥ 3 chars — near-certain or absent: undefined name 'totl' — no binding on any path (did you mean 'total'?)

Verification

  • Calibration gate: zero E003 across lib/ + examples/ + tests/ and ALL 10 consumer repos (one FP breaks consumer CI — the gate is the contract)
  • test_lint.sh 72/72 — 4 new firing fixtures + a runtime-verified silent fixture (runs clean under the interpreter, exit 0)
  • Full suite 2609/2609 release AND ASan+UBSan detect_leaks=1, leak tally 0
  • LSP 67/67 + test_lsp_asan.sh clean — the LSP shares the pass and inherits the precision
  • Existing E003 fixtures unchanged and passing (suggestion suffix is additive)

Also fixes a stale DIAGNOSTICS.md sentence left by #406 (the E100 note still described catch binding the error string).

🤖 Generated with Claude Code

…ement two)

The undefined-name pass now models the runtime's real scope rules
instead of a whole-file over-approximated binding set — each rule
pinned empirically against the interpreter before implementation:

- a fresh-name 'is' (or 'local') inside a function is FUNCTION-LOCAL:
  invisible to sibling functions and module code (new true positives)
- closures read enclosing function scopes (lexical Env parent chain)
- module names stay order-insensitive (a body may read a module name
  bound after the definition)
- a nested 'define' binds its name in the enclosing function only
- a module-level 'for' LOOP-SCOPES its variable — the VM drops it at
  loop exit, so a post-loop read is a runtime error the lint now
  catches; a function-level 'for' var survives (matching the VM, and
  the exact divergence class ouroboros PR #79 hit)
- listcomp vars and catch vars bind in the containing scope

Scope chain IS the Env parent chain: COLLECT creates one Env per
scope-introducing node in walk order, FLAG replays them by counter
(both walks visit the same nodes in the same order by construction).
Within a scope the binder set stays path-insensitive ('bound on some
path' suppresses); path precision is #404's last increment. External
files (load_file targets, loaded-by composers) still contribute a flat
over-approximation — scope-narrowing someone else's file buys precision
nobody asked for at real false-positive risk.

Messages gain an edit-distance-1 near-miss suggestion against the
visible binding set: "did you mean 'total'?" (distance 1 only, names
>= 3 chars — near-certain or absent).

Calibration gate: zero E003 across lib/+examples/+tests/ and ALL 10
consumer repos. Fixtures: 4 new firing classes + a runtime-verified
silent fixture (the program runs clean under the interpreter). Suite
2609/2609 release + ASan detect_leaks=1 tally 0; LSP 67/67 + ASan
clean (the LSP shares the pass and inherits the precision).

Also fixes a stale DIAGNOSTICS.md claim left by #406 (E100 note still
said catch binds the error string).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@InauguralPhysicist InauguralPhysicist merged commit 68f7927 into main Jul 7, 2026
17 checks passed
@InauguralPhysicist InauguralPhysicist deleted the feat-404-scoped-e003 branch July 7, 2026 10:52
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.

1 participant