From 1abcac9c1630b66038fa611005cbd37266a7a79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Magyar?= Date: Sat, 18 Jul 2026 14:30:37 +0100 Subject: [PATCH] fix(scope-resolution): stop platform builtins resolving to unrelated same-file symbols (#2549) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(scope-resolution): stop platform builtins resolving to unrelated same-file symbols (#2545) An unqualified call to a platform/language builtin (e.g. TypeScript's global fetch()) could resolve to an unrelated same-file declaration sharing that name, most visibly a Cloudflare Worker's `export default { async fetch(req) {...} }` handler. Two contributing gaps, both fixed: - Object literals had no scope boundary in the TS/JS grammar queries, so a method's/property-arrow's name auto-hoisted past the literal into whatever lexically enclosed it (scope-extractor.ts's auto-hoist logic had nowhere to stop). Give object literals a Block scope, like 6 other languages already do for lexical blocks. - Independently, finalize's per-file bindings bucket (materializeBindings in gitnexus-shared) flattens every local declaration in a file onto its module scope for cross-file import resolution, regardless of true nesting -- so free-call-fallback's scope-chain walk could still hit the leaked binding at module scope. Guard free-call resolution: when a match for a known builtin name (LanguageProvider.isBuiltInName, already populated for TS/JS but never consulted by this pass) has no binding reachable via the true lexical scope chain, leave the call unresolved instead of emitting a false CALLS edge. Verified against the full TS/JS resolver suites plus every other language populating builtInNames (Python, Go, C/C++, C#, Dart, Kotlin, PHP, Ruby, Rust, Swift, Vue) -- 2333 tests, no regressions. Co-Authored-By: Claude Sonnet 5 * fix(scope-resolution): extend the #2545 scope-leak fix to Kotlin and Java Anonymous object-expressions (Kotlin `object { ... }`) and anonymous class bodies (Java `new Runnable() { ... }`) have the same missing scope-boundary gap that caused #2545 in TypeScript/JavaScript: a method declared inside has no scope of its own to stop the auto-hoist at, so its name leaks past the container into the enclosing scope. - Kotlin: `(object_literal) @scope.class` (distinct from the already- scoped named `object_declaration`/`companion_object`). Kotlin already populates `builtInNames`, so free-call-fallback's isBuiltInName guard (added for #2545) fully closes the equivalent leak here too -- verified with a `println`-shadowing regression test. - Java: `(object_creation_expression (class_body) @scope.class)`, matching PHP's existing `anonymous_class` handling. Java has no `builtInNames` list, so the isBuiltInName guard doesn't engage -- the scope-tree fix is still correct and necessary (the anonymous class's own methods are now owned by the right scope), but an unqualified call to an unrelated same-file method sharing the anonymous class's method name can still resolve via finalize's per-file module-scope bucket (materializeBindings, shared/ language-agnostic, intentionally not touched by this PR). Documented in the test as a known residual gap, same as TS/JS/Kotlin's own non-builtin-name collisions. Audited every other language for the same shape (a value/container node with no @scope.* capture hosting a would-be-auto-hoisted named declaration): PHP and Vue already handle it correctly (PHP scopes anonymous_class; Vue's