fix(compile): a namespace member named like a global must not shadow it - #11833
Conversation
A namespace import registers every member of the imported module in the flat import_function_prefixes map as a best-effort fallback for bare-name calls (#5927). For a member named like a global intrinsic that entry hijacks the importer's own bare global: lower_new finds the name in the map and constructs the member instead of the builtin, so new Array(n).fill(0).length came back undefined. effect's Array.ts exports `const Array = globalThis.Array`, so every effect module that does `import * as Arr from "./Array.ts"` and writes `new Array(n)` referenced Array.ts's wrapper under a name the owning module never emits, and the OpenCode v1.18.30 link failed (#10945). Skip the flat, bare-name entries for global-intrinsic names, as #10356 does for implicitly registered classes. The per-namespace entries are kept, so ns.Array and other members still resolve.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
📒 Files selected for processing (2)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
With this PR plus #11817 and #11819 on Compared with the official bun build of the same checkout (
The TUI abort is |
Fixes #10945.
Symptom
The OpenCode v1.18.30 link fails on an undefined wrapper for effect's
Arrayexport:The references come from closures in effect's
Chunk.ts,Cron.tsandinternal/effect.ts. The same bug gives wrong answers in programs that do link. Two modules:With
import { f }in place of the namespace import, or with no import at all, perry prints3.Cause
Not the auto-optimize pass the issue suspected: the link failed identically in a build where that pass never ran. And not a missing definition:
Array.ts's own object defines the wrapper and its info record as…__Array/…__Array$info, while the failing importers ask for…__Array_$info.A namespace import registers every member of the imported module in the flat
import_function_prefixesmap, as a best-effort fallback for bare-name calls (#5927). The code's own comments say genuine namespace-member accesses are resolved authoritatively through the per-namespacenamespace_member_prefixesmap. effect'sArray.tsexportsconst Array = globalThis.Array, so the flat map gains an entry forArray.lower_new(from #10589) treats any name in that map as a user-owned constructor:So the importer's own bare
new Array(n)constructs the member instead of the builtin. effect'sChunk.ts,Cron.tsandinternal/effect.tsall doimport * as … from "./Array.ts"and writenew Array(n). That path names the wrapper under a symbol the owning module never emits, and the link fails.This is the class #10356 fixed for classes — an implicitly registered export shadowing a global intrinsic — reached here through the namespace-import fallback for functions and values.
Fix
In both namespace fallback loops —
import * as Xand a namespace re-export (export * as X) — skip the flat, bare-name entries for namesis_global_intrinsic_value_namerecognises, exactly as #10356 does for classes. Five insertions per loop are gated:import_function_prefixes,import_function_origin_names, and the bare-key entries ofimported_param_counts,imported_has_restandimported_synthetic_arguments. The per-namespace entries (namespace_member_prefixes,namespace_member_origin_names, the scoped-key metadata) are untouched, soA.Arrayand every other member still resolve. An explicit named import goes through the specifier-driven path and is unaffected. A bareArrayin a module that never imported a bareArraycan only mean the global.Test
crates/perry/tests/issue_10945_namespace_member_shadows_global.rscompiles a four-module program end to end and compares its output byte for byte with node 26.5.1's:new Array(n)inside a function,Array.isArray,Array.from, andArray.ofinside a closure — the importer's globals;Arr.Array === globalThis.Array,new Arr.Array(2)andArr.bump(41)— namespace members must keep resolving;import { Map } from "./fake_map.js"— Importing one name from a module also binds that module's OTHER exported classes — a userclass Requestshadows the globalRequestin the importer (OpenCode TUI bootstrap wall) #10356's control: an explicit named import of a global-shadowing class must still win.Both arms were run as real commits, with the runtime libraries rebuilt from each, so a failure can only be the program's output and never a build-stamp mismatch:
run_pipeline.rsreverted: fails at the output assertion with1 new Array(n): undefined. Lines 2–8 are identical in both arms, so the controls were never affected.cargo fmt --all -- --checkis clean. With this and #11817 / #11819, the OpenCode v1.18.30 build gets through codegen and past this symbol at link; a full build with all three is running and I will post its result.