@@ -126,6 +126,127 @@ import Testing
126126 try snapshot ( bridgeJSLink: bridgeJSLink, name: " MixedModules " )
127127 }
128128
129+ private func buildSkeleton(
130+ moduleName: String ,
131+ source: String ,
132+ inputFilePath: String = " input.swift "
133+ ) throws -> BridgeJSSkeleton {
134+ let swiftAPI = SwiftToSkeleton (
135+ progress: . silent,
136+ moduleName: moduleName,
137+ exposeToGlobal: false ,
138+ externalModuleIndex: . empty
139+ )
140+ swiftAPI. addSourceFile ( Parser . parse ( source: source) , inputFilePath: inputFilePath)
141+ return try swiftAPI. finalize ( )
142+ }
143+
144+ @Test
145+ func sameNamedTypesAcrossModulesLinkWithDistinctABINames( ) throws {
146+ // Both modules export a `Point` struct and a `run` function with identical Swift
147+ // names but different public JS names (one is namespaced). The module-qualified
148+ // ABI names must keep the generated glue (struct hooks, helper keys, and
149+ // function thunk names) distinct, while the public JS/TS surface stays flat.
150+ let moduleA = try buildSkeleton (
151+ moduleName: " ModuleA " ,
152+ source: """
153+ @JS public struct Point {
154+ public let x: Double
155+ public let y: Double
156+ @JS public init(x: Double, y: Double) {
157+ self.x = x
158+ self.y = y
159+ }
160+ }
161+ @JS public enum Validation {
162+ case valid
163+ case invalid(reason: String)
164+ }
165+ @JS public func run(point: Point, validation: Validation) -> Point {
166+ point
167+ }
168+ """
169+ )
170+ let moduleB = try buildSkeleton (
171+ moduleName: " ModuleB " ,
172+ source: """
173+ @JS(namespace: " Inner " ) public struct Point {
174+ public let x: Double
175+ public let y: Double
176+ @JS public init(x: Double, y: Double) {
177+ self.x = x
178+ self.y = y
179+ }
180+ }
181+ @JS(namespace: " Inner " ) public func run() {
182+ }
183+ """
184+ )
185+ let bridgeJSLink = BridgeJSLink ( skeletons: [ moduleA, moduleB] , sharedMemory: false )
186+
187+ let ( outputJs, outputDts) = try bridgeJSLink. link ( )
188+
189+ // Struct hooks and helper keys are module-qualified and distinct.
190+ #expect( outputJs. contains ( " swift_js_struct_lower_ModuleA_Point " ) )
191+ #expect( outputJs. contains ( " swift_js_struct_lower_ModuleB_Point " ) )
192+ #expect( outputJs. contains ( " structHelpers.ModuleA_Point " ) )
193+ #expect( outputJs. contains ( " structHelpers.ModuleB_Point " ) )
194+ // Associated-value enum helper keys are module-qualified.
195+ #expect( outputJs. contains ( " enumHelpers.ModuleA_Validation " ) )
196+ // Exported function thunk names are module-qualified and distinct.
197+ #expect( outputJs. contains ( " instance.exports.bjs_ModuleA_run " ) )
198+ #expect( outputJs. contains ( " instance.exports.bjs_ModuleB_Inner_run " ) )
199+ // The public TS surface stays flat (no module prefixes).
200+ #expect( !outputDts. contains ( " ModuleA " ) )
201+ #expect( !outputDts. contains ( " ModuleB " ) )
202+
203+ try snapshot ( bridgeJSLink: bridgeJSLink, name: " SameNamedTypesAcrossModules " )
204+ }
205+
206+ @Test
207+ func duplicatePublicNamesAcrossModulesProduceDiagnostic( ) throws {
208+ let source = """
209+ @JS public struct Point {
210+ public let x: Double
211+ @JS public init(x: Double) {
212+ self.x = x
213+ }
214+ }
215+ """
216+ let moduleA = try buildSkeleton ( moduleName: " ModuleA " , source: source)
217+ let moduleB = try buildSkeleton ( moduleName: " ModuleB " , source: source)
218+ let bridgeJSLink = BridgeJSLink ( skeletons: [ moduleA, moduleB] , sharedMemory: false )
219+
220+ do {
221+ _ = try bridgeJSLink. link ( )
222+ Issue . record ( " Expected duplicate public export name diagnostic, but linking succeeded " )
223+ } catch let error as BridgeJSLinkError {
224+ #expect( error. message. contains ( " Duplicate exported name 'Point' " ) )
225+ #expect( error. message. contains ( " ModuleA " ) )
226+ #expect( error. message. contains ( " ModuleB " ) )
227+ }
228+ }
229+
230+ @Test
231+ func duplicatePublicFunctionNamesAcrossModulesProduceDiagnostic( ) throws {
232+ let moduleA = try buildSkeleton (
233+ moduleName: " ModuleA " ,
234+ source: " @JS public func run() {} "
235+ )
236+ let moduleB = try buildSkeleton (
237+ moduleName: " ModuleB " ,
238+ source: " @JS public func run() {} "
239+ )
240+ let bridgeJSLink = BridgeJSLink ( skeletons: [ moduleA, moduleB] , sharedMemory: false )
241+
242+ do {
243+ _ = try bridgeJSLink. link ( )
244+ Issue . record ( " Expected duplicate public export name diagnostic, but linking succeeded " )
245+ } catch let error as BridgeJSLinkError {
246+ #expect( error. message. contains ( " Duplicate exported name 'run' " ) )
247+ }
248+ }
249+
129250 @Test
130251 func perClassIdentityModeFromAnnotation( ) throws {
131252 let url = Self . inputsDirectory. appendingPathComponent ( " IdentityModeClass.swift " )
0 commit comments