Summary
Follow-up to #5922/#5923: fixing the import_function_prefixes/namespace_member_prefixes collision alone was not enough for a real-world sst/opencode compile — a companion map, import_function_origin_names, has the exact same flat-map collision architecture and independently broke the same real code.
provider.ts does export class Service extends Context.Service<Service, Interface>()("@opencode/Provider") {} — a genuine call to Context.Service, where Context is reached via import { Effect, Layer, Context, Schema, Types } from "effect" (a named import of a value the source module re-exports as a namespace).
Context.ts (effect's real TS source) directly exports Service — no rename needed. But linking failed with:
Undefined symbols for architecture arm64:
"_perry_fn_..._effect_src_Context_ts__a", referenced from:
_perry_closure_packages_opencode_src_provider_provider_ts__297
_perry_closure_packages_opencode_src_provider_provider_ts__307
Note the symbol suffix is __a, not __Service — the compiler is looking for the wrong symbol entirely, not just at the wrong module.
Root cause
import_function_origin_names: HashMap<String, String> (issue #678) records symbol-suffix overrides for re-export renames (export { default as render } → call perry_fn_<origin>__default, not __render). It's a flat map keyed by bare member name, populated once per specifier in the handled_as_namespace_reexport loop (run_pipeline.rs) that processes import { Effect, Layer, Context, Schema, Types } from "effect" — one iteration per specifier, all writing into the same shared map for this file.
Effect (processed before Context in specifier order) apparently re-exports something named Service from an internal chunk under a different origin identifier (a rename), so its iteration inserts import_function_origin_names["Service"] = "<effect's internal origin name>". When Context's own turn comes (a direct, unrenamed Service export), no override is needed, so nothing overwrites the earlier (wrong) entry — it survives in the flat map. Codegen then resolves Context.Service via this contaminated flat entry instead of Context's own (correct, unrenamed) symbol.
This is architecturally identical to #5922/#680: the fix there added a per-namespace-scoped companion map (namespace_member_prefixes) for import_function_prefixes. import_function_origin_names needs the same treatment (namespace_member_origin_names, keyed by (namespace_local, member_name)), consulted by every codegen site that builds a perry_fn_<src>__<suffix> symbol for a namespace-member access (expr/static_method.rs, lower_call/namespace_call.rs, expr/property_get.rs).
One subtlety the #5922 fix didn't need: a sparse per-namespace map (only inserting entries when a rename applies) is not sufficient here, because an unrenamed member with no entry in the per-namespace map would still fall through to the (possibly contaminated) flat map. The fix registers every namespace member unconditionally (renamed or not), mirroring how namespace_member_prefixes is already populated for every export, not just the interesting ones.
Repro
// ns_a_impl.ts
export const a = () => "NsA-Service"
// ns_a.ts — re-export rename: origin name "a" != export name "Service"
export { a as Service } from "./ns_a_impl.ts"
// ns_b.ts — direct export, no rename
export const Service = () => "NsB-Service"
// barrel.ts
export * as NsA from "./ns_a.ts"
export * as NsB from "./ns_b.ts"
// main.ts
import { NsA, NsB } from "./barrel.ts"
console.log(NsA.Service())
console.log(NsB.Service())
Node: NsA-Service / NsB-Service.
perry (pre-fix): link failure, perry_fn_..._ns_b_ts__a undefined — NsB.Service() incorrectly tries to resolve via NsA's rename.
Impact
This was the actual remaining blocker for a real-world sst/opencode native compile after #5918/#5919 and #5922/#5923 — Context.Service/Option-adjacent effect namespace members.
PR incoming.
Summary
Follow-up to #5922/#5923: fixing the
import_function_prefixes/namespace_member_prefixescollision alone was not enough for a real-worldsst/opencodecompile — a companion map,import_function_origin_names, has the exact same flat-map collision architecture and independently broke the same real code.provider.tsdoesexport class Service extends Context.Service<Service, Interface>()("@opencode/Provider") {}— a genuine call toContext.Service, whereContextis reached viaimport { Effect, Layer, Context, Schema, Types } from "effect"(a named import of a value the source module re-exports as a namespace).Context.ts(effect's real TS source) directly exportsService— no rename needed. But linking failed with:Note the symbol suffix is
__a, not__Service— the compiler is looking for the wrong symbol entirely, not just at the wrong module.Root cause
import_function_origin_names: HashMap<String, String>(issue #678) records symbol-suffix overrides for re-export renames (export { default as render }→ callperry_fn_<origin>__default, not__render). It's a flat map keyed by bare member name, populated once per specifier in thehandled_as_namespace_reexportloop (run_pipeline.rs) that processesimport { Effect, Layer, Context, Schema, Types } from "effect"— one iteration per specifier, all writing into the same shared map for this file.Effect(processed beforeContextin specifier order) apparently re-exports something namedServicefrom an internal chunk under a different origin identifier (a rename), so its iteration insertsimport_function_origin_names["Service"] = "<effect's internal origin name>". WhenContext's own turn comes (a direct, unrenamedServiceexport), no override is needed, so nothing overwrites the earlier (wrong) entry — it survives in the flat map. Codegen then resolvesContext.Servicevia this contaminated flat entry instead of Context's own (correct, unrenamed) symbol.This is architecturally identical to #5922/#680: the fix there added a per-namespace-scoped companion map (
namespace_member_prefixes) for import_function_prefixes.import_function_origin_namesneeds the same treatment (namespace_member_origin_names, keyed by(namespace_local, member_name)), consulted by every codegen site that builds aperry_fn_<src>__<suffix>symbol for a namespace-member access (expr/static_method.rs,lower_call/namespace_call.rs,expr/property_get.rs).One subtlety the #5922 fix didn't need: a sparse per-namespace map (only inserting entries when a rename applies) is not sufficient here, because an unrenamed member with no entry in the per-namespace map would still fall through to the (possibly contaminated) flat map. The fix registers every namespace member unconditionally (renamed or not), mirroring how
namespace_member_prefixesis already populated for every export, not just the interesting ones.Repro
Node:
NsA-Service/NsB-Service.perry (pre-fix): link failure,
perry_fn_..._ns_b_ts__aundefined —NsB.Service()incorrectly tries to resolve viaNsA's rename.Impact
This was the actual remaining blocker for a real-world
sst/opencodenative compile after #5918/#5919 and #5922/#5923 —Context.Service/Option-adjacent effect namespace members.PR incoming.