feat(lint): E003 scope-precise binding sets + did-you-mean (#404 increment two)#470
Merged
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:isorlocal)define(enclosing-local by the runtime's rules)forvariable (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
Envparent 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_filetargets,# lint: loaded-bycomposers) 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
detect_leaks=1, leak tally 0Also fixes a stale DIAGNOSTICS.md sentence left by #406 (the E100 note still described catch binding the error string).
🤖 Generated with Claude Code