What
docs/plans/issue-2088.md's escape analysis reuses introducesShadowedBinding (src/extractors/javascript.ts:4693) as the shadow-detection check both findDeclaringScopeNode/allReferencesTracked (condition 3, #2260's own reference-walk boundary) and, via findResolvingScopeNode, condition 4's identifier resolution are built on.
introducesShadowedBinding's statement_block case (javascript.ts:4744-4771) inspects only lexical_declaration (via declarationDeclaresName) and, by name field, function_declaration/generator_function_declaration/class_declaration direct children when deciding whether a block directly declares the checked name. Verified against tree-sitter-javascript@0.25.0's own node-types.json: the grammar has a distinct using_declaration node type (using run = mk(); / await using run = mk();, the Explicit Resource Management declaration form) — it is NEITHER a lexical_declaration NOR any of the other three checked types, so introducesShadowedBinding's statement_block case has no branch for it at all and falls through to default: return false. A block-scoped using run = mk(); is therefore invisible to the shadow check both findDeclaringScopeNode (condition 3) and, via findResolvingScopeNode, condition 4's resolution question rely on.
Why this matters, and why it's the safe direction (for one consumer, not the other)
Two call sites are affected identically, since both ultimately consult introducesShadowedBinding for the same statement_block case:
- findDeclaringScopeNode's ancestor walk (condition 3, allReferencesTracked's reference-walk boundary, Computed-property (bracket-access) dispatch-table lookups lack a real calls edge, unlike dot-property value-refs #2260) — a using-declared shadow inside a block would not prune the walk there, so a reference to it could be wrongly attributed to an OUTER, same-named binding instead. This is the safe direction for allReferencesTracked's own contract (a conjunction over every found reference — an extra, spuriously-counted reference can only push the result toward false/escaping, the same reasoning issue-2088 plan: introducesShadowedBinding's arrow_function case can't see a bare single-identifier parameter, under-counting shadows for condition 3's reference walk #2629 already gives for the sibling arrow-bare-parameter gap in this same primitive).
- findResolvingScopeNode (condition 4's identifier resolution, PR [Plan] issue-2088: receiver-correlated invoked-property evidence for object-literal value-refs #2612's plan) — a same-named module-level decoy shadowed only by a block-scoped using run = mk(); would resolve to the decoy instead of failing safe, a CONFIDENTLY WRONG answer rather than an honest "unresolved" one — the same failure class round 10/11/12 already closed for other shadow shapes in this same function.
Exploiting the gap for condition 4 specifically requires the using-declared value to be a function-shaped object also carrying Symbol.dispose (or Symbol.asyncDispose, for await using) — using is designed for disposable resources, not ordinary functions — making this considerably more exotic than the arrow-bare-parameter (#2629) or for-of/for-in-parenthesized-target (#2630) gaps already filed against this same primitive, both of which arise from completely ordinary syntax.
Suggested fix shape (not binding — decide at execute/fix time)
Add a using_declaration case alongside the existing lexical_declaration one in introducesShadowedBinding's statement_block branch — using_declaration has the identical variable_declarator-list child shape declarationDeclaresName already expects (verified against the grammar: both node types' children are variable_declarator), so the existing declarationDeclaresName(child, name) call needs no changes, only a widened child.type === 'lexical_declaration' || child.type === 'using_declaration' guard. Mirror the identical fix in the Rust introduces_shadowed_binding (javascript.rs, if/when WU-7 lands). Add a fixture proving a block-scoped using declaration correctly shadows for both findDeclaringScopeNode and findResolvingScopeNode's purposes once fixed.
Where
What
docs/plans/issue-2088.md's escape analysis reuses introducesShadowedBinding (src/extractors/javascript.ts:4693) as the shadow-detection check both findDeclaringScopeNode/allReferencesTracked (condition 3, #2260's own reference-walk boundary) and, via findResolvingScopeNode, condition 4's identifier resolution are built on.
introducesShadowedBinding's statement_block case (javascript.ts:4744-4771) inspects only lexical_declaration (via declarationDeclaresName) and, by name field, function_declaration/generator_function_declaration/class_declaration direct children when deciding whether a block directly declares the checked name. Verified against tree-sitter-javascript@0.25.0's own node-types.json: the grammar has a distinct using_declaration node type (using run = mk(); / await using run = mk();, the Explicit Resource Management declaration form) — it is NEITHER a lexical_declaration NOR any of the other three checked types, so introducesShadowedBinding's statement_block case has no branch for it at all and falls through to default: return false. A block-scoped using run = mk(); is therefore invisible to the shadow check both findDeclaringScopeNode (condition 3) and, via findResolvingScopeNode, condition 4's resolution question rely on.
Why this matters, and why it's the safe direction (for one consumer, not the other)
Two call sites are affected identically, since both ultimately consult introducesShadowedBinding for the same statement_block case:
Exploiting the gap for condition 4 specifically requires the using-declared value to be a function-shaped object also carrying Symbol.dispose (or Symbol.asyncDispose, for await using) — using is designed for disposable resources, not ordinary functions — making this considerably more exotic than the arrow-bare-parameter (#2629) or for-of/for-in-parenthesized-target (#2630) gaps already filed against this same primitive, both of which arise from completely ordinary syntax.
Suggested fix shape (not binding — decide at execute/fix time)
Add a using_declaration case alongside the existing lexical_declaration one in introducesShadowedBinding's statement_block branch — using_declaration has the identical variable_declarator-list child shape declarationDeclaresName already expects (verified against the grammar: both node types' children are variable_declarator), so the existing declarationDeclaresName(child, name) call needs no changes, only a widened child.type === 'lexical_declaration' || child.type === 'using_declaration' guard. Mirror the identical fix in the Rust introduces_shadowed_binding (javascript.rs, if/when WU-7 lands). Add a fixture proving a block-scoped using declaration correctly shadows for both findDeclaringScopeNode and findResolvingScopeNode's purposes once fixed.
Where