swiftwasm / swiftwasm/JavaScriptKit
BridgeJS: Same-named `@JS` types across modules collide silently in generated glue
@krodak ya está trabajando en esto.
Desde el 6/7/2026.
- Lenguaje dominante
- Swift
- Estrellas
- 986
- Forks
- 76
- Merge medio
- 21 h 11 min
- PR fusionados (30 d)
- 4
Descripción
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.
Public JS declarations (fails loudly, related): even when the colliding types live under different @JS(namespace:) paths — where no public-surface conflict exists — the generated JS still declares classes and enum value objects with flat top-level identifiers (class Point { … }, const PointValues = …) before wiring them into their namespace objects, and bridge-js.d.ts emits the corresponding declarations unscoped. Two same-named types in different namespaces therefore produce duplicate declarations in one scope: a load-time SyntaxError on the JS side and duplicate-identifier errors in the .d.ts. Unlike the ABI surfaces above this fails loudly rather than corrupting data, but it makes same-named types unusable across modules even when their public paths don't overlap.
Possible direction
- Make type identity in the skeleton module-aware (defining module + name). #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 access paths flat (
exports.Point, namespace objects, TS types) and emit a build-time linker diagnostic when two modules expose the identical public name at the same namespace path, mirroring how #730 handles Swift-side ambiguity with a diagnostic that asks for explicit qualification. - For the declaration surface: declare classes and enum value objects under unique internal identifiers (the module-qualified ABI name is a natural choice), bind public names only through namespace objects /
exports, and scope.d.tsdeclarations for namespaced types insidedeclare namespaceblocks. Public access paths stay exactly as today; only the internal declaration identifiers change, so same-named types in different namespaces become fully usable.
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Evaluación
Este issue todavía no se ha evaluado.