@@ -536,12 +536,12 @@ public final class SwiftToSkeleton {
536536
537537 if let typeDecl = typeDeclResolver. resolve ( type) {
538538 if typeDecl. is ( ProtocolDeclSyntax . self) {
539- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: typeDecl, itemName: typeDecl. name. text)
539+ let swiftCallName = computeSwiftCallName ( for: typeDecl, itemName: typeDecl. name. text)
540540 return . swiftProtocol( swiftCallName)
541541 }
542542
543543 if let enumDecl = typeDecl. as ( EnumDeclSyntax . self) {
544- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: enumDecl, itemName: enumDecl. name. text)
544+ let swiftCallName = computeSwiftCallName ( for: enumDecl, itemName: enumDecl. name. text)
545545 if let jsAttribute = enumDecl. attributes. firstJSAttribute,
546546 let aliasTarget = extractAliasTarget ( from: jsAttribute)
547547 {
@@ -580,7 +580,7 @@ public final class SwiftToSkeleton {
580580 }
581581
582582 if let structDecl = typeDecl. as ( StructDeclSyntax . self) {
583- let swiftCallName = SwiftToSkeleton . computeSwiftCallName (
583+ let swiftCallName = computeSwiftCallName (
584584 for: structDecl,
585585 itemName: structDecl. name. text
586586 )
@@ -598,7 +598,7 @@ public final class SwiftToSkeleton {
598598 guard typeDecl. is ( ClassDeclSyntax . self) || typeDecl. is ( ActorDeclSyntax . self) else {
599599 return nil
600600 }
601- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: typeDecl, itemName: typeDecl. name. text)
601+ let swiftCallName = computeSwiftCallName ( for: typeDecl, itemName: typeDecl. name. text)
602602
603603 // A type annotated with @JSClass is a JavaScript object wrapper (imported),
604604 // even if it is declared as a Swift class.
@@ -638,7 +638,7 @@ public final class SwiftToSkeleton {
638638 private func resolveExternal( for type: TypeSyntax , errors: inout [ DiagnosticError ] ) -> BridgeType ? {
639639 guard
640640 !externalModuleIndex. isEmpty,
641- var components = typeDeclResolver . qualifiedComponents ( from : type )
641+ var components = type . qualifiedComponents
642642 else {
643643 return nil
644644 }
@@ -777,27 +777,50 @@ public final class SwiftToSkeleton {
777777 return nil
778778 }
779779
780- /// Computes the full Swift call name by walking up the AST hierarchy to find all parent enums
780+ /// This currently doesn’t work correctly for extensions on types defined in other modules,
781+ /// which is fine for now since we don’t support extending @JS types from other modules.
782+ /// This will need updating when we do.
783+ fileprivate func enclosingDeclarations( of node: some SyntaxProtocol ) -> [ Syntax ] {
784+ var declarations : [ Syntax ] = [ ]
785+ var visitedExtendedTypes : Set < SyntaxIdentifier > = [ ]
786+ var currentNode : Syntax ? = Syntax ( node) . parent
787+
788+ while let parent = currentNode {
789+ if let extensionDecl = parent. as ( ExtensionDeclSyntax . self) {
790+ if let extendedDecl = typeDeclResolver. resolve ( extensionDecl. extendedType) ,
791+ visitedExtendedTypes. insert ( extendedDecl. id) . inserted
792+ {
793+ declarations. append ( Syntax ( extendedDecl) )
794+ currentNode = Syntax ( extendedDecl) . parent
795+ } else {
796+ currentNode = parent. parent
797+ }
798+ } else {
799+ declarations. append ( parent)
800+ currentNode = parent. parent
801+ }
802+ }
803+ return declarations
804+ }
805+
781806 /// This generates the qualified name needed for Swift code generation (e.g., "Networking.API.HTTPServer")
782- fileprivate static func computeSwiftCallName( for node: some SyntaxProtocol , itemName: String ) -> String {
807+ fileprivate func computeSwiftCallName( for node: some SyntaxProtocol , itemName: String ) -> String {
783808 var swiftPath : [ String ] = [ ]
784- var currentNode : Syntax ? = node. parent
785809
786- while let parent = currentNode {
787- if let enumDecl = parent . as ( EnumDeclSyntax . self) ,
810+ for declaration in enclosingDeclarations ( of : node ) {
811+ if let enumDecl = declaration . as ( EnumDeclSyntax . self) ,
788812 enumDecl. attributes. hasJSAttribute ( )
789813 {
790814 swiftPath. insert ( enumDecl. name. text, at: 0 )
791- } else if let structDecl = parent . as ( StructDeclSyntax . self) ,
815+ } else if let structDecl = declaration . as ( StructDeclSyntax . self) ,
792816 structDecl. attributes. hasJSAttribute ( )
793817 {
794818 swiftPath. insert ( structDecl. name. text, at: 0 )
795- } else if let classDecl = parent . as ( ClassDeclSyntax . self) ,
819+ } else if let classDecl = declaration . as ( ClassDeclSyntax . self) ,
796820 classDecl. attributes. hasJSAttribute ( )
797821 {
798822 swiftPath. insert ( classDecl. name. text, at: 0 )
799823 }
800- currentNode = parent. parent
801824 }
802825
803826 if swiftPath. isEmpty {
@@ -1883,7 +1906,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
18831906 resolvedNamespace: namespaceResult. namespace,
18841907 parentTypeNamespace: computeParentTypeNamespace ( for: node)
18851908 )
1886- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: node, itemName: name)
1909+ let swiftCallName = parent . computeSwiftCallName ( for: node, itemName: name)
18871910 let explicitAccessControl = computeExplicitAtLeastInternalAccessControl (
18881911 for: node,
18891912 message: " Class visibility must be at least internal "
@@ -1949,25 +1972,23 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
19491972 }
19501973
19511974 /// Walks extension members under the matching type’s state, returning whether the type was found.
1952- ///
1953- /// Note: The lookup scans dictionaries keyed by `makeKey(name:namespace:)`, matching only by
1954- /// plain name. If two types share a name but differ by namespace, `.first(where:)` picks
1955- /// whichever comes first. This is acceptable today since namespace collisions are unlikely,
1956- /// but may need refinement if namespace-qualified extension resolution is added.
19571975 func resolveExtension( _ ext: ExtensionDeclSyntax ) -> Bool {
1958- let name = ext. extendedType. trimmedDescription
1976+ guard let extendedDecl = parent. typeDeclResolver. resolve ( ext. extendedType) else {
1977+ return false
1978+ }
1979+ let swiftCallName = parent. computeSwiftCallName ( for: extendedDecl, itemName: extendedDecl. name. text)
19591980 let state : State
1960- if let entry = exportedClassByName. first ( where: { $0. value. name == name } ) {
1961- state = . classBody( name: name, key: entry. key)
1962- } else if let entry = exportedStructByName. first ( where: { $0. value. name == name } ) {
1963- state = . structBody( name: name, key: entry. key)
1964- } else if let entry = exportedEnumByName. first ( where: { $0. value. name == name } ) {
1965- state = . enumBody( name: name, key: entry. key)
1966- } else if exportedProtocolByName. values. contains ( where: { $0. name == name } ) {
1981+ if let entry = exportedClassByName. first ( where: { $0. value. swiftCallName == swiftCallName } ) {
1982+ state = . classBody( name: entry . value . name, key: entry. key)
1983+ } else if let entry = exportedStructByName. first ( where: { $0. value. swiftCallName == swiftCallName } ) {
1984+ state = . structBody( name: entry . value . name, key: entry. key)
1985+ } else if let entry = exportedEnumByName. first ( where: { $0. value. swiftCallName == swiftCallName } ) {
1986+ state = . enumBody( name: entry . value . name, key: entry. key)
1987+ } else if exportedProtocolByName. values. contains ( where: { $0. name == swiftCallName } ) {
19671988 diagnose (
19681989 node: ext. extendedType,
19691990 message: " Protocol extensions are not supported by BridgeJS. " ,
1970- hint: " You cannot extend `@JS` protocol ' \( name ) ' with additional members "
1991+ hint: " You cannot extend `@JS` protocol ' \( swiftCallName ) ' with additional members "
19711992 )
19721993 return true
19731994 } else {
@@ -1986,7 +2007,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
19862007 jsAttribute: AttributeSyntax ,
19872008 aliasTarget: TypeSyntax
19882009 ) {
1989- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: node, itemName: node. name. text)
2010+ let swiftCallName = parent . computeSwiftCallName ( for: node, itemName: node. name. text)
19902011 if extractNamespace ( from: jsAttribute) != nil {
19912012 errors. append (
19922013 DiagnosticError (
@@ -2051,7 +2072,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
20512072 parentTypeNamespace: computeParentTypeNamespace ( for: node)
20522073 )
20532074 let emitStyle = extractEnumStyle ( from: jsAttribute) ?? . const
2054- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: node, itemName: name)
2075+ let swiftCallName = parent . computeSwiftCallName ( for: node, itemName: name)
20552076 let explicitAccessControl = computeExplicitAtLeastInternalAccessControl (
20562077 for: node,
20572078 message: " Enum visibility must be at least internal "
@@ -2237,7 +2258,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
22372258 resolvedNamespace: namespaceResult. namespace,
22382259 parentTypeNamespace: computeParentTypeNamespace ( for: node)
22392260 )
2240- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: node, itemName: name)
2261+ let swiftCallName = parent . computeSwiftCallName ( for: node, itemName: name)
22412262 let explicitAccessControl = computeExplicitAtLeastInternalAccessControl (
22422263 for: node,
22432264 message: " Struct visibility must be at least internal "
@@ -2552,10 +2573,9 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
25522573 /// Method allows for explicit namespace for top level enum, it will be used as base namespace and will concat enum name
25532574 private func computeNamespace( for node: some SyntaxProtocol ) -> [ String ] ? {
25542575 var namespace : [ String ] = [ ]
2555- var currentNode : Syntax ? = node. parent
25562576
2557- while let parent = currentNode {
2558- if let enumDecl = parent . as ( EnumDeclSyntax . self) ,
2577+ for declaration in parent. enclosingDeclarations ( of : node ) {
2578+ if let enumDecl = declaration . as ( EnumDeclSyntax . self) ,
25592579 enumDecl. attributes. hasJSAttribute ( )
25602580 {
25612581 let isNamespaceEnum = !enumDecl. memberBlock. members. contains { member in
@@ -2572,27 +2592,24 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
25722592 }
25732593 }
25742594 }
2575- currentNode = parent. parent
25762595 }
25772596
25782597 return namespace. isEmpty ? nil : namespace
25792598 }
25802599
25812600 private func computeParentTypeNamespace( for node: some SyntaxProtocol ) -> [ String ] ? {
25822601 var path : [ String ] = [ ]
2583- var currentNode : Syntax ? = node. parent
25842602
2585- while let parent = currentNode {
2586- if let structDecl = parent . as ( StructDeclSyntax . self) ,
2603+ for declaration in parent. enclosingDeclarations ( of : node ) {
2604+ if let structDecl = declaration . as ( StructDeclSyntax . self) ,
25872605 structDecl. attributes. hasJSAttribute ( )
25882606 {
25892607 path. insert ( structDecl. name. text, at: 0 )
2590- } else if let classDecl = parent . as ( ClassDeclSyntax . self) ,
2608+ } else if let classDecl = declaration . as ( ClassDeclSyntax . self) ,
25912609 classDecl. attributes. hasJSAttribute ( )
25922610 {
25932611 path. insert ( classDecl. name. text, at: 0 )
25942612 }
2595- currentNode = parent. parent
25962613 }
25972614
25982615 return path. isEmpty ? nil : path
0 commit comments