@@ -525,12 +525,12 @@ public final class SwiftToSkeleton {
525525
526526 if let typeDecl = typeDeclResolver. resolve ( type) {
527527 if typeDecl. is ( ProtocolDeclSyntax . self) {
528- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: typeDecl, itemName: typeDecl. name. text)
528+ let swiftCallName = computeSwiftCallName ( for: typeDecl, itemName: typeDecl. name. text)
529529 return . swiftProtocol( swiftCallName)
530530 }
531531
532532 if let enumDecl = typeDecl. as ( EnumDeclSyntax . self) {
533- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: enumDecl, itemName: enumDecl. name. text)
533+ let swiftCallName = computeSwiftCallName ( for: enumDecl, itemName: enumDecl. name. text)
534534 if let jsAttribute = enumDecl. attributes. firstJSAttribute,
535535 let aliasTarget = extractAliasTarget ( from: jsAttribute)
536536 {
@@ -569,7 +569,7 @@ public final class SwiftToSkeleton {
569569 }
570570
571571 if let structDecl = typeDecl. as ( StructDeclSyntax . self) {
572- let swiftCallName = SwiftToSkeleton . computeSwiftCallName (
572+ let swiftCallName = computeSwiftCallName (
573573 for: structDecl,
574574 itemName: structDecl. name. text
575575 )
@@ -587,7 +587,7 @@ public final class SwiftToSkeleton {
587587 guard typeDecl. is ( ClassDeclSyntax . self) || typeDecl. is ( ActorDeclSyntax . self) else {
588588 return nil
589589 }
590- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: typeDecl, itemName: typeDecl. name. text)
590+ let swiftCallName = computeSwiftCallName ( for: typeDecl, itemName: typeDecl. name. text)
591591
592592 // A type annotated with @JSClass is a JavaScript object wrapper (imported),
593593 // even if it is declared as a Swift class.
@@ -627,7 +627,7 @@ public final class SwiftToSkeleton {
627627 private func resolveExternal( for type: TypeSyntax , errors: inout [ DiagnosticError ] ) -> BridgeType ? {
628628 guard
629629 !externalModuleIndex. isEmpty,
630- var components = typeDeclResolver . qualifiedComponents ( from : type )
630+ var components = type . qualifiedComponents
631631 else {
632632 return nil
633633 }
@@ -766,27 +766,50 @@ public final class SwiftToSkeleton {
766766 return nil
767767 }
768768
769- /// Computes the full Swift call name by walking up the AST hierarchy to find all parent enums
769+ /// This currently doesn’t work correctly for extensions on types defined in other modules,
770+ /// which is fine for now since we don’t support extending @JS types from other modules.
771+ /// This will need updating when we do.
772+ fileprivate func enclosingDeclarations( of node: some SyntaxProtocol ) -> [ Syntax ] {
773+ var declarations : [ Syntax ] = [ ]
774+ var visitedExtendedTypes : Set < SyntaxIdentifier > = [ ]
775+ var currentNode : Syntax ? = Syntax ( node) . parent
776+
777+ while let parent = currentNode {
778+ if let extensionDecl = parent. as ( ExtensionDeclSyntax . self) {
779+ if let extendedDecl = typeDeclResolver. resolve ( extensionDecl. extendedType) ,
780+ visitedExtendedTypes. insert ( extendedDecl. id) . inserted
781+ {
782+ declarations. append ( Syntax ( extendedDecl) )
783+ currentNode = Syntax ( extendedDecl) . parent
784+ } else {
785+ currentNode = parent. parent
786+ }
787+ } else {
788+ declarations. append ( parent)
789+ currentNode = parent. parent
790+ }
791+ }
792+ return declarations
793+ }
794+
770795 /// This generates the qualified name needed for Swift code generation (e.g., "Networking.API.HTTPServer")
771- fileprivate static func computeSwiftCallName( for node: some SyntaxProtocol , itemName: String ) -> String {
796+ fileprivate func computeSwiftCallName( for node: some SyntaxProtocol , itemName: String ) -> String {
772797 var swiftPath : [ String ] = [ ]
773- var currentNode : Syntax ? = node. parent
774798
775- while let parent = currentNode {
776- if let enumDecl = parent . as ( EnumDeclSyntax . self) ,
799+ for declaration in enclosingDeclarations ( of : node ) {
800+ if let enumDecl = declaration . as ( EnumDeclSyntax . self) ,
777801 enumDecl. attributes. hasJSAttribute ( )
778802 {
779803 swiftPath. insert ( enumDecl. name. text, at: 0 )
780- } else if let structDecl = parent . as ( StructDeclSyntax . self) ,
804+ } else if let structDecl = declaration . as ( StructDeclSyntax . self) ,
781805 structDecl. attributes. hasJSAttribute ( )
782806 {
783807 swiftPath. insert ( structDecl. name. text, at: 0 )
784- } else if let classDecl = parent . as ( ClassDeclSyntax . self) ,
808+ } else if let classDecl = declaration . as ( ClassDeclSyntax . self) ,
785809 classDecl. attributes. hasJSAttribute ( )
786810 {
787811 swiftPath. insert ( classDecl. name. text, at: 0 )
788812 }
789- currentNode = parent. parent
790813 }
791814
792815 if swiftPath. isEmpty {
@@ -1861,7 +1884,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
18611884 resolvedNamespace: namespaceResult. namespace,
18621885 parentTypeNamespace: computeParentTypeNamespace ( for: node)
18631886 )
1864- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: node, itemName: name)
1887+ let swiftCallName = parent . computeSwiftCallName ( for: node, itemName: name)
18651888 let explicitAccessControl = computeExplicitAtLeastInternalAccessControl (
18661889 for: node,
18671890 message: " Class visibility must be at least internal "
@@ -1921,25 +1944,23 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
19211944 }
19221945
19231946 /// Walks extension members under the matching type’s state, returning whether the type was found.
1924- ///
1925- /// Note: The lookup scans dictionaries keyed by `makeKey(name:namespace:)`, matching only by
1926- /// plain name. If two types share a name but differ by namespace, `.first(where:)` picks
1927- /// whichever comes first. This is acceptable today since namespace collisions are unlikely,
1928- /// but may need refinement if namespace-qualified extension resolution is added.
19291947 func resolveExtension( _ ext: ExtensionDeclSyntax ) -> Bool {
1930- let name = ext. extendedType. trimmedDescription
1948+ guard let extendedDecl = parent. typeDeclResolver. resolve ( ext. extendedType) else {
1949+ return false
1950+ }
1951+ let swiftCallName = parent. computeSwiftCallName ( for: extendedDecl, itemName: extendedDecl. name. text)
19311952 let state : State
1932- if let entry = exportedClassByName. first ( where: { $0. value. name == name } ) {
1933- state = . classBody( name: name, key: entry. key)
1934- } else if let entry = exportedStructByName. first ( where: { $0. value. name == name } ) {
1935- state = . structBody( name: name, key: entry. key)
1936- } else if let entry = exportedEnumByName. first ( where: { $0. value. name == name } ) {
1937- state = . enumBody( name: name, key: entry. key)
1938- } else if exportedProtocolByName. values. contains ( where: { $0. name == name } ) {
1953+ if let entry = exportedClassByName. first ( where: { $0. value. swiftCallName == swiftCallName } ) {
1954+ state = . classBody( name: entry . value . name, key: entry. key)
1955+ } else if let entry = exportedStructByName. first ( where: { $0. value. swiftCallName == swiftCallName } ) {
1956+ state = . structBody( name: entry . value . name, key: entry. key)
1957+ } else if let entry = exportedEnumByName. first ( where: { $0. value. swiftCallName == swiftCallName } ) {
1958+ state = . enumBody( name: entry . value . name, key: entry. key)
1959+ } else if exportedProtocolByName. values. contains ( where: { $0. name == swiftCallName } ) {
19391960 diagnose (
19401961 node: ext. extendedType,
19411962 message: " Protocol extensions are not supported by BridgeJS. " ,
1942- hint: " You cannot extend `@JS` protocol ' \( name ) ' with additional members "
1963+ hint: " You cannot extend `@JS` protocol ' \( swiftCallName ) ' with additional members "
19431964 )
19441965 return true
19451966 } else {
@@ -1958,7 +1979,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
19581979 jsAttribute: AttributeSyntax ,
19591980 aliasTarget: TypeSyntax
19601981 ) {
1961- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: node, itemName: node. name. text)
1982+ let swiftCallName = parent . computeSwiftCallName ( for: node, itemName: node. name. text)
19621983 if extractNamespace ( from: jsAttribute) != nil {
19631984 errors. append (
19641985 DiagnosticError (
@@ -2023,7 +2044,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
20232044 parentTypeNamespace: computeParentTypeNamespace ( for: node)
20242045 )
20252046 let emitStyle = extractEnumStyle ( from: jsAttribute) ?? . const
2026- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: node, itemName: name)
2047+ let swiftCallName = parent . computeSwiftCallName ( for: node, itemName: name)
20272048 let explicitAccessControl = computeExplicitAtLeastInternalAccessControl (
20282049 for: node,
20292050 message: " Enum visibility must be at least internal "
@@ -2209,7 +2230,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
22092230 resolvedNamespace: namespaceResult. namespace,
22102231 parentTypeNamespace: computeParentTypeNamespace ( for: node)
22112232 )
2212- let swiftCallName = SwiftToSkeleton . computeSwiftCallName ( for: node, itemName: name)
2233+ let swiftCallName = parent . computeSwiftCallName ( for: node, itemName: name)
22132234 let explicitAccessControl = computeExplicitAtLeastInternalAccessControl (
22142235 for: node,
22152236 message: " Struct visibility must be at least internal "
@@ -2524,10 +2545,9 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
25242545 /// Method allows for explicit namespace for top level enum, it will be used as base namespace and will concat enum name
25252546 private func computeNamespace( for node: some SyntaxProtocol ) -> [ String ] ? {
25262547 var namespace : [ String ] = [ ]
2527- var currentNode : Syntax ? = node. parent
25282548
2529- while let parent = currentNode {
2530- if let enumDecl = parent . as ( EnumDeclSyntax . self) ,
2549+ for declaration in parent. enclosingDeclarations ( of : node ) {
2550+ if let enumDecl = declaration . as ( EnumDeclSyntax . self) ,
25312551 enumDecl. attributes. hasJSAttribute ( )
25322552 {
25332553 let isNamespaceEnum = !enumDecl. memberBlock. members. contains { member in
@@ -2544,27 +2564,24 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
25442564 }
25452565 }
25462566 }
2547- currentNode = parent. parent
25482567 }
25492568
25502569 return namespace. isEmpty ? nil : namespace
25512570 }
25522571
25532572 private func computeParentTypeNamespace( for node: some SyntaxProtocol ) -> [ String ] ? {
25542573 var path : [ String ] = [ ]
2555- var currentNode : Syntax ? = node. parent
25562574
2557- while let parent = currentNode {
2558- if let structDecl = parent . as ( StructDeclSyntax . self) ,
2575+ for declaration in parent. enclosingDeclarations ( of : node ) {
2576+ if let structDecl = declaration . as ( StructDeclSyntax . self) ,
25592577 structDecl. attributes. hasJSAttribute ( )
25602578 {
25612579 path. insert ( structDecl. name. text, at: 0 )
2562- } else if let classDecl = parent . as ( ClassDeclSyntax . self) ,
2580+ } else if let classDecl = declaration . as ( ClassDeclSyntax . self) ,
25632581 classDecl. attributes. hasJSAttribute ( )
25642582 {
25652583 path. insert ( classDecl. name. text, at: 0 )
25662584 }
2567- currentNode = parent. parent
25682585 }
25692586
25702587 return path. isEmpty ? nil : path
0 commit comments