@@ -521,7 +521,9 @@ public struct BridgeJSLink {
521521
522522 private func generateAddImports( needsImportsObject: Bool ) throws -> CodeFragmentPrinter {
523523 let printer = CodeFragmentPrinter ( )
524- let allStructs = skeletons. compactMap { $0. exported? . structs } . flatMap { $0 }
524+ let allStructs = skeletons. flatMap { unified in
525+ ( unified. exported? . structs ?? [ ] ) . map { ( moduleName: unified. moduleName, structDef: $0) }
526+ }
525527 printer. write ( " return { " )
526528 try printer. indent {
527529 printer. write ( lines: [
@@ -669,19 +671,24 @@ public struct BridgeJSLink {
669671 }
670672 printer. write ( " } " )
671673 if !allStructs. isEmpty {
672- for structDef in allStructs {
674+ for (moduleName, structDef) in allStructs {
675+ // The `bjs` import names are part of the wasm ABI and are
676+ // minted from the ABI name alone on both sides, so they
677+ // cannot be module-qualified here. `validateNoCrossModuleTypeNameCollisions`
678+ // rejects the inputs that would make them collide.
679+ let key = HelperNaming . qualified ( base: structDef. abiName, module: moduleName)
673680 printer. write ( " bjs[ \" swift_js_struct_lower_ \( structDef. abiName) \" ] = function(objectId) { " )
674681 printer. indent {
675682 printer. write (
676- " \( JSGlueVariableScope . reservedStructHelpers) . \( structDef . abiName ) .lower( \( JSGlueVariableScope . reservedSwift) .memory.getObject(objectId)); "
683+ " \( JSGlueVariableScope . reservedStructHelpers) . \( key ) .lower( \( JSGlueVariableScope . reservedSwift) .memory.getObject(objectId)); "
677684 )
678685 }
679686 printer. write ( " } " )
680687
681688 printer. write ( " bjs[ \" swift_js_struct_lift_ \( structDef. abiName) \" ] = function() { " )
682689 printer. indent {
683690 printer. write (
684- " const value = \( JSGlueVariableScope . reservedStructHelpers) . \( structDef . abiName ) .lift(); "
691+ " const value = \( JSGlueVariableScope . reservedStructHelpers) . \( key ) .lift(); "
685692 )
686693 printer. write ( " return \( JSGlueVariableScope . reservedSwift) .memory.retain(value); " )
687694 }
@@ -1255,12 +1262,18 @@ public struct BridgeJSLink {
12551262
12561263 let bodyPrinter = CodeFragmentPrinter ( )
12571264 let allStructs = exportedSkeletons. flatMap { $0. structs }
1258- for structDef in allStructs {
1265+ for (moduleName, structDef) in skeletons. flatMap ( { unified in
1266+ ( unified. exported? . structs ?? [ ] ) . map { ( unified. moduleName, $0) }
1267+ } ) {
12591268 let structPrinter = CodeFragmentPrinter ( )
12601269 let structScope = JSGlueVariableScope ( intrinsicRegistry: intrinsicRegistry)
1261- let fragment = IntrinsicJSFragment . structHelper ( structDefinition: structDef, allStructs: allStructs)
1270+ let fragment = IntrinsicJSFragment . structHelper (
1271+ structDefinition: structDef,
1272+ allStructs: allStructs,
1273+ moduleName: moduleName
1274+ )
12621275 _ = try fragment. printCode (
1263- [ structDef . abiName ] ,
1276+ [ ] ,
12641277 IntrinsicJSFragment . PrintCodeContext (
12651278 scope: structScope,
12661279 printer: structPrinter,
@@ -1271,13 +1284,16 @@ public struct BridgeJSLink {
12711284 bodyPrinter. write ( lines: structPrinter. lines)
12721285 }
12731286
1274- let allAssocEnums = exportedSkeletons . flatMap {
1275- $0 . enums. filter { $0. enumType == . associatedValue }
1276- }
1277- for enumDef in allAssocEnums {
1287+ for (moduleName , enumDef ) in skeletons . flatMap ( { unified in
1288+ ( unified . exported ? . enums ?? [ ] ) . filter { $0. enumType == . associatedValue }
1289+ . map { ( unified . moduleName , $0 ) }
1290+ } ) {
12781291 let enumPrinter = CodeFragmentPrinter ( )
12791292 let enumScope = JSGlueVariableScope ( intrinsicRegistry: intrinsicRegistry)
1280- let fragment = IntrinsicJSFragment . associatedValueEnumHelperFactory ( enumDefinition: enumDef)
1293+ let fragment = IntrinsicJSFragment . associatedValueEnumHelperFactory (
1294+ enumDefinition: enumDef,
1295+ moduleName: moduleName
1296+ )
12811297 _ = try fragment. printCode (
12821298 [ enumDef. valuesName] ,
12831299 IntrinsicJSFragment . PrintCodeContext (
@@ -1402,6 +1418,7 @@ public struct BridgeJSLink {
14021418 }
14031419
14041420 public func link( ) throws -> ( outputJs: String , outputDts: String ) {
1421+ try validateNoCrossModuleTypeNameCollisions ( )
14051422 intrinsicRegistry. reset ( )
14061423 importedModuleRegistry. configure ( skeletons: skeletons)
14071424 intrinsicRegistry. classNamespaces = skeletons. reduce ( into: [ : ] ) { result, unified in
@@ -1419,6 +1436,74 @@ public struct BridgeJSLink {
14191436 return ( outputJs, outputDts)
14201437 }
14211438
1439+ /// Rejects skeletons in which two modules declare an exported type under the
1440+ /// same name.
1441+ ///
1442+ /// Declaration-site identifiers are module-qualified, but three things
1443+ /// downstream of them are minted from the bare type name and cannot be:
1444+ ///
1445+ /// - `BridgeType` carries only a type name, so a reference to `Point` from
1446+ /// module *B* cannot tell which module's `structHelpers` entry it means.
1447+ /// - The top-level `const <Enum>Values` object and the `class <Name>`
1448+ /// declaration are part of the public JS surface, so both modules would
1449+ /// emit the same `const` / `class` in one glue module.
1450+ /// - `bjs["swift_js_struct_lower_<AbiName>"]` is a wasm import name minted
1451+ /// from the ABI name on the Swift side too, so both modules import the
1452+ /// same one.
1453+ ///
1454+ /// Emitting glue anyway would either throw at load time or silently bind one
1455+ /// module's values to the other module's helpers, so this fails the link.
1456+ func validateNoCrossModuleTypeNameCollisions( ) throws {
1457+ /// The identifier minted for a declaration, and where it comes from.
1458+ struct Declaration {
1459+ let moduleName : String
1460+ let kind : String
1461+ }
1462+ var declarations : [ String : [ Declaration ] ] = [ : ]
1463+ func record( _ mintedName: String , kind: String , module: String ) {
1464+ // Two declarations from the *same* module colliding is a separate,
1465+ // pre-existing issue (`@JS` types of the same name in different
1466+ // namespaces); this check is only about cross-module collisions.
1467+ guard !( declarations [ mintedName] ?? [ ] ) . contains ( where: { $0. moduleName == module } ) else { return }
1468+ declarations [ mintedName, default: [ ] ] . append ( Declaration ( moduleName: module, kind: kind) )
1469+ }
1470+
1471+ for unified in skeletons {
1472+ guard let skeleton = unified. exported else { continue }
1473+ let module = unified. moduleName
1474+ for structDef in skeleton. structs {
1475+ record ( structDef. abiName, kind: " struct " , module: module)
1476+ }
1477+ for klass in skeleton. classes {
1478+ record ( klass. name, kind: " class " , module: module)
1479+ }
1480+ // Namespace enums declare no runtime value of their own; they only
1481+ // contribute to the merged namespace objects, which already merge
1482+ // across modules.
1483+ for enumDef in skeleton. enums where enumDef. enumType != . namespace {
1484+ record ( enumDef. name, kind: " enum " , module: module)
1485+ }
1486+ }
1487+
1488+ for (mintedName, declarations) in declarations. sorted ( by: { $0. key < $1. key } ) where declarations. count > 1 {
1489+ let modules = declarations. map ( \. moduleName) . sorted ( )
1490+ let kinds = Set ( declarations. map ( \. kind) ) . sorted ( ) . joined ( separator: " / " )
1491+ throw BridgeJSLinkError (
1492+ message: """
1493+ Duplicate @JS \( kinds) ' \( mintedName) ' declared by modules \
1494+ \( modules. map { " ' \( $0) ' " } . joined ( separator: " and " ) ) .
1495+
1496+ The generated JavaScript glue mints identifiers from the type name \
1497+ (the helper tables, the top-level declarations, and the wasm import names \
1498+ the Swift side derives from the same name), so the two declarations \
1499+ would collide in a single glue module.
1500+
1501+ Rename one of them so that each module declares a distinct name.
1502+ """
1503+ )
1504+ }
1505+ }
1506+
14221507 /// Maps every type name a `BridgeType` can carry to the module that declares
14231508 /// it, so identifiers minted from type names can be module-qualified.
14241509 ///
@@ -1463,12 +1548,13 @@ public struct BridgeJSLink {
14631548 private func enumHelperAssignments( ) -> CodeFragmentPrinter {
14641549 let printer = CodeFragmentPrinter ( )
14651550
1466- for skeleton in skeletons. compactMap ( \. exported) {
1551+ for unified in skeletons {
1552+ guard let skeleton = unified. exported else { continue }
14671553 for enumDef in skeleton. enums where enumDef. enumType == . associatedValue {
1468- printer . write (
1469- " const \( enumDef . name ) Helpers = __bjs_create \( enumDef . valuesName ) Helpers(); "
1470- )
1471- printer. write ( " \( JSGlueVariableScope . reservedEnumHelpers) . \( enumDef . name ) = \( enumDef . name ) Helpers ;" )
1554+ let key = HelperNaming . qualified ( base : enumDef . name , module : unified . moduleName )
1555+ let local = HelperNaming . helperConstant ( key )
1556+ printer . write ( " const \( local ) = \( HelperNaming . enumHelperFactory ( key ) ) (); " )
1557+ printer. write ( " \( JSGlueVariableScope . reservedEnumHelpers) . \( key ) = \( local ) ; " )
14721558 printer. nextLine ( )
14731559 }
14741560 }
@@ -1479,14 +1565,13 @@ public struct BridgeJSLink {
14791565 private func structHelperAssignments( ) -> CodeFragmentPrinter {
14801566 let printer = CodeFragmentPrinter ( )
14811567
1482- for skeleton in skeletons. compactMap ( \. exported) {
1568+ for unified in skeletons {
1569+ guard let skeleton = unified. exported else { continue }
14831570 for structDef in skeleton. structs {
1484- printer. write (
1485- " const \( structDef. abiName) Helpers = __bjs_create \( structDef. abiName) Helpers(); "
1486- )
1487- printer. write (
1488- " \( JSGlueVariableScope . reservedStructHelpers) . \( structDef. abiName) = \( structDef. abiName) Helpers; "
1489- )
1571+ let key = HelperNaming . qualified ( base: structDef. abiName, module: unified. moduleName)
1572+ let local = HelperNaming . helperConstant ( key)
1573+ printer. write ( " const \( local) = \( HelperNaming . structHelperFactory ( key) ) (); " )
1574+ printer. write ( " \( JSGlueVariableScope . reservedStructHelpers) . \( key) = \( local) ; " )
14901575 printer. nextLine ( )
14911576 }
14921577 }
@@ -4278,8 +4363,10 @@ private struct DocCComment {
42784363 }
42794364}
42804365
4281- struct BridgeJSLinkError : Error {
4366+ struct BridgeJSLinkError : Error , CustomStringConvertible {
42824367 let message : String
4368+
4369+ var description : String { message }
42834370}
42844371
42854372extension BridgeType {
0 commit comments