BridgeJS: Module-qualify internal ABI names to fix cross-module type collisions#17
Closed
krodak wants to merge 2 commits into
Closed
BridgeJS: Module-qualify internal ABI names to fix cross-module type collisions#17krodak wants to merge 2 commits into
krodak wants to merge 2 commits into
Conversation
…collisions When two Swift modules in one BridgeJS build exported a `@JS` type with the same name, the generated glue collided silently: duplicated `@_expose(wasm, ...)` thunk names, duplicated `swift_js_struct_lower_<name>`/`swift_js_struct_lift_<name>` hooks, and colliding `structHelpers`/`enumHelpers` JS keys - last writer wins, causing silent data corruption. Fix by module-qualifying the internal ABI identity of exported Swift types while keeping the public JS/TS surface flat: - `BridgeType` payloads for exported Swift types are minted as `<Module>.<Nesting>.<Name>` (still a valid Swift call-site expression). - Type `abiName`s are derived from the module-qualified `swiftCallName` (dots replaced with underscores), so the JS helper key derived from a payload always equals the defining type's `abiName` by construction. - Top-level function thunk names gain the module name as namespace context; enum static contexts carry the qualified `swiftCallName` (sanitized inside `ABINameGenerator`). - `mangleTypeName` sanitizes dots since mangled names are embedded in Swift identifiers. - `ExternalModuleIndex` keeps indexing dependency types by their module-stripped dot path while carrying qualified payloads, so cross-module references resolve to the same ABI names. - Public JS class names, exports keys, and TS declarations stay flat via the new `BridgeType.dropModulePrefix` display accessor. - BridgeJSLink now fails with a clear diagnostic when two modules export the same public name at the same namespace path. Fixes swiftwasm#785
- Link two skeletons that both export a `Point` struct and a `run` function with identical Swift names but different public JS names, and assert the generated glue keeps module-qualified struct hooks, helper keys, and thunk names distinct while the TS surface stays flat (snapshot: SameNamedTypesAcrossModules). - Assert that two modules exporting the same public name (type or function) at the same namespace path fail to link with the new duplicate-export diagnostic.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes swiftwasm#785.
Problem
When two Swift modules in a single BridgeJS build both export a
@JStype with the same name (e.g.Point), the generated glue collides silently:@_expose(wasm, "bjs_Point_init")thunk names,swift_js_struct_lower_Point/swift_js_struct_lift_Pointextern hooks and their JS-sidebjs[...]handlers,structHelpers.Point/enumHelpers.Pointkeys in the shared JS glue.The JS handlers are plain assignments, so the last-linked module wins and every other module's values are decoded with the wrong field schema — silent data corruption rather than a build error.
Approach
Type identity in the generated glue is now module-aware, while the public JS surface stays flat:
BridgeTypepayloads are minted asModule.[Nesting.]Nameat skeleton-generation time. Cross-module references resolve throughExternalModuleIndex, which indexes by module-stripped path (so existingPoint/Dep.Pointreferences keep working) while carrying the defining module's qualified payload.abiName = swiftCallName.replacing(".", "_"), so the invariant JS key derived from payload == defining type's helper key holds structurally instead of by parallel derivations. Allbjs_*thunks,swift_js_struct_lower/lift_*externs, andstructHelpers/enumHelperskeys becomeModule_…-qualified.exportskeys, and TypeScript declarations are untouched — the re-record produced zero.d.tsdiffs. Display names strip the module prefix.Protocol method hooks (wasm imports, already scoped per module by the import object) and TS-imported types keep their existing naming.
Testing
sameNamedTypesAcrossModulesLinkWithDistinctABINames— links two skeletons that each define@JS struct Pointplus same-named top-level functions; asserts distinct module-qualified externs and helper keys in the output (snapshot:SameNamedTypesAcrossModules.js/.d.ts).duplicatePublicNamesAcrossModulesProduceDiagnostic/duplicatePublicFunctionNamesAcrossModulesProduceDiagnostic— the new linker error../Utilities/bridge-js-generate.shzero drift;npm run check:bridgejs-dtspasses; WASM E2E (make unittest) 32/32 suites pass.Notes for reviewers
@JS(namespace:)components no longer appear in type abiNames (module + Swift nesting path is used instead, which is unique per module and fixes a latent invariant violation for attribute-namespaced structs).class X/XValuesemissions). That's a pre-existing public-surface limitation, distinct from the ABI corruption fixed here — happy to file a follow-up issue.