Skip to content

fix(compile): a namespace member named like a global must not shadow it - #11833

Merged
proggeramlug merged 2 commits into
mainfrom
fix/10945-namespace-member-shadows-global
Oct 3, 2026
Merged

proggeramlug merged 2 commits into
mainfrom
fix/10945-namespace-member-shadows-global

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #10945.

Symptom

The OpenCode v1.18.30 link fails on an undefined wrapper for effect's Array export:

undefined reference to `__perry_wrap_perry_fn_…effect_src_Array_ts__Array_$info'

The references come from closures in effect's Chunk.ts, Cron.ts and internal/effect.ts. The same bug gives wrong answers in programs that do link. Two modules:

// a.ts
export const Array = globalThis.Array
export function f(x: number) { return x + 1 }

// main.ts
import * as A from "./a.ts"
function inner(n: number) { return new Array(n).fill(0).length }
console.log(inner(3))      // node: 3   perry on main: undefined

With import { f } in place of the namespace import, or with no import at all, perry prints 3.

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_prefixes map, 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-namespace namespace_member_prefixes map. effect's Array.ts exports const Array = globalThis.Array, so the flat map gains an entry for Array. lower_new (from #10589) treats any name in that map as a user-owned constructor:

let user_owns_construction = ctx.import_function_prefixes.contains_key(class_name) && …;
if !ctx.classes.contains_key(class_name) && !user_owns_construction { /* builtin `new` */ }

So the importer's own bare new Array(n) constructs the member instead of the builtin. effect's Chunk.ts, Cron.ts and internal/effect.ts all do import * as … from "./Array.ts" and write new 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 X and a namespace re-export (export * as X) — skip the flat, bare-name entries for names is_global_intrinsic_value_name recognises, exactly as #10356 does for classes. Five insertions per loop are gated: import_function_prefixes, import_function_origin_names, and the bare-key entries of imported_param_counts, imported_has_rest and imported_synthetic_arguments. The per-namespace entries (namespace_member_prefixes, namespace_member_origin_names, the scoped-key metadata) are untouched, so A.Array and every other member still resolve. An explicit named import goes through the specifier-driven path and is unaffected. A bare Array in a module that never imported a bare Array can only mean the global.

Test

crates/perry/tests/issue_10945_namespace_member_shadows_global.rs compiles a four-module program end to end and compares its output byte for byte with node 26.5.1's:

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:

  • with the fix: passes;
  • with run_pipeline.rs reverted: fails at the output assertion with 1 new Array(n): undefined. Lines 2–8 are identical in both arms, so the controls were never affected.

cargo fmt --all -- --check is 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.

Ralph Küpper added 2 commits October 3, 2026 20:59
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.
@coderabbitai

coderabbitai Bot commented Oct 3, 2026

Copy link
Copy Markdown

Review in Change Stack →

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
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 123bf58a-c263-4461-a558-08fb96df6bde
📥 Commits

Reviewing files that changed from the base of the PR and between 72eafb8 and 25867e0.

📒 Files selected for processing (2)
  • crates/perry/src/commands/compile/run_pipeline.rs
  • crates/perry/tests/issue_10945_namespace_member_shadows_global.rs
 ______________________________
< torvalds@linux:~$ git review >
 ------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug
proggeramlug merged commit 2f48477 into main Oct 3, 2026
32 of 35 checks passed
@proggeramlug
proggeramlug deleted the fix/10945-namespace-member-shadows-global branch October 3, 2026 19:20
@proggeramlug

Copy link
Copy Markdown
Contributor Author

With this PR plus #11817 and #11819 on main 681d7024, the full OpenCode v1.18.30 build now compiles and links natively on Linux x86_64: perry compile exits 0 after 68 minutes and produces a 1.08 GB binary, with auto-optimize on (runtime, stdlib and ext-http rebuilt for the program's feature set).

Compared with the official bun build of the same checkout (packages/opencode/script/build.ts --single):

bun perry
--version rc 0 rc 0
--help rc 0, 4285 B rc 0, 4285 B
models rc 0, 850 B rc 0, 815 B — one entry missing
TUI (pty) rc 124, 75 truecolor cells rc 134 — a static-ShapeId abort at startup
--version startup 444 ms 2700 ms

The TUI abort is the static ShapeId 0x80015cb7 (proto 0x0) was refused by the shape mint, which is unrelated to these three fixes. I am root-causing it and will file it separately. opencode run still reports "Session not found", which is #10531.

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.

OpenCode link fails on main: undefined __perry_wrap_perry_fn_…effect_src_Array_ts__Array_ (and PERRY_NO_AUTO_OPTIMIZE=1 hits a tokio archive mismatch)

1 participant