Skip to content

BridgeJS: Same-named @JS types across modules collide silently in generated glue #785

Description

@krodak

Overview

Since #730, @JS types defined in one module can be referenced from @JS APIs in other modules within the same package. However, type identity in the skeleton and in the generated glue is the unqualified type name — abiName carries the namespace but not the module. When two linked modules define @JS types with the same name (e.g. @JS struct Point in both ModuleA and ModuleB), several generated-name surfaces collide:

Stack ABI intrinsics (silent data corruption): Both modules' generated Swift declares

@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_Point")

WebAssembly imports with the same module + name unify, and the linker assigns bjs["swift_js_struct_lower_Point"] twice — last one wins. Both modules end up calling the same handler, so one module's structs are encoded/decoded with the other's field layout. No diagnostic at build time or runtime.

JS helper tables: structHelpers / enum helper factories are keyed by unqualified name inside createInstantiator — same last-wins behavior.

Exposed thunks: @_expose(wasm, "bjs_Point_init") (and method/deinit thunks) are emitted by both modules, producing duplicate export names.

TypeScript declarations: two interface Point declarations in one bridge-js.d.ts — TypeScript declaration-merges them, which is silent when the field sets are compatible and produces confusing errors when they are not.

Reference resolution: independent of naming, a skeleton reference like .swiftStruct("Point") is unqualified, so the linker resolves it by name-matching across all skeletons — with two candidates the choice is arbitrary. Renaming the ABI surfaces alone is not sufficient; the reference side needs the defining module too.

Possible direction

  • Make type identity in the skeleton module-aware (defining module + name). BridgeJS: Use @JS types from other modules in the same package #730 already established that the skeleton format does not need to stay backward compatible.
  • Module-qualify the internal ABI names (swift_js_struct_lower_<Module>_<Name>, bjs_<Module>_…). Newer name families already follow this pattern — closure invokes (invoke_js_callback_<module>_<mangleName>) and promise settlement helpers (promise_resolve_<module>_<mangled>) — it is only the older core names that predate it.
  • Keep the public JS surface flat (exports.Point, TS declarations) and emit a build-time linker diagnostic when two modules expose the same public name, mirroring how BridgeJS: Use @JS types from other modules in the same package #730 handles Swift-side ambiguity with a diagnostic that asks for explicit qualification.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions