swiftwasm / swiftwasm/JavaScriptKit

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

Abierto
#785 0 comentarios 0 reacciones 1 asignado Ver en GitHub

@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.ts declarations for namespaced types inside declare namespace blocks. 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

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.