Skip to content

Commit 372fce2

Browse files
committed
BridgeJS: Resolve qualified types relative to lexical scope
1 parent 7a7a70a commit 372fce2

7 files changed

Lines changed: 1211 additions & 23 deletions

File tree

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ public final class SwiftToSkeleton {
818818

819819
while let parent = currentNode {
820820
if let extensionDecl = parent.as(ExtensionDeclSyntax.self) {
821-
if let extendedDecl = typeDeclResolver.resolve(extensionDecl.extendedType),
821+
if let extendedDecl = typeDeclResolver.resolveExtensionTarget(extensionDecl.extendedType),
822822
visitedExtendedTypes.insert(extendedDecl.id).inserted
823823
{
824824
declarations.append(Syntax(extendedDecl))
@@ -1996,7 +1996,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
19961996

19971997
/// Walks extension members under the matching type’s state, returning whether the type was found.
19981998
func resolveExtension(_ ext: ExtensionDeclSyntax) -> Bool {
1999-
guard let extendedDecl = parent.typeDeclResolver.resolve(ext.extendedType) else {
1999+
guard let extendedDecl = parent.typeDeclResolver.resolveExtensionTarget(ext.extendedType) else {
20002000
return false
20012001
}
20022002
let swiftCallName = parent.computeSwiftCallName(for: extendedDecl, itemName: extendedDecl.name.text)

Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class TypeDeclResolver {
9292
}
9393

9494
/// Builds the type name scope for a given type usage
95-
private func buildScope(type: IdentifierTypeSyntax) -> QualifiedName {
95+
private func buildScope(type: TypeSyntax) -> QualifiedName {
9696
var innerToOuter: [String] = []
9797
var context: SyntaxProtocol = type
9898
while let parent = context.parent {
@@ -108,25 +108,29 @@ class TypeDeclResolver {
108108
return innerToOuter.reversed()
109109
}
110110

111-
/// Looks up a qualified name of a type declaration by its unqualified type usage
111+
/// Looks up a qualified name of a type declaration relative to its lexical scope
112112
/// Returns the qualified name hierarchy of the type declaration
113-
/// If the type declaration is not found, returns the unqualified name
114-
private func tryQualify(type: IdentifierTypeSyntax) -> QualifiedName {
115-
let name = type.name.text
113+
/// If the type declaration is not found, returns the original qualified name
114+
private func tryQualify(type: TypeSyntax) -> QualifiedName? {
115+
guard let components = type.qualifiedComponents else {
116+
return nil
117+
}
116118
let scope = buildScope(type: type)
117119
/// Search for the type declaration from the innermost scope to the outermost scope
118120
for i in (0...scope.count).reversed() {
119-
let qualifiedName = Array(scope[0..<i] + [name])
121+
let qualifiedName = Array(scope.prefix(i)) + components
120122
if typeDeclByQualifiedName[qualifiedName] != nil || typeAliasByQualifiedName[qualifiedName] != nil {
121123
return qualifiedName
122124
}
123125
}
124-
return [name]
126+
return components
125127
}
126128

127129
/// Looks up a type declaration by its unqualified type usage
128130
func lookupType(for type: IdentifierTypeSyntax) -> TypeDecl? {
129-
let qualifiedName = tryQualify(type: type)
131+
guard let qualifiedName = tryQualify(type: TypeSyntax(type)) else {
132+
return nil
133+
}
130134
return typeDeclByQualifiedName[qualifiedName]
131135
}
132136

@@ -139,36 +143,35 @@ class TypeDeclResolver {
139143
///
140144
/// Supported inputs:
141145
/// - IdentifierTypeSyntax (e.g. `Method`) — resolved relative to the lexical scope, preferring the innermost enclosing type.
142-
/// - MemberTypeSyntax (e.g. `Networking.API.Method`) — resolved by recursively building the fully qualified name.
146+
/// - MemberTypeSyntax (e.g. `Networking.API.Method`) — resolved relative to the lexical scope before falling back to the fully qualified name.
143147
///
144148
/// Resolution strategy:
145-
/// 1. If the node is IdentifierTypeSyntax, call `lookupType(for:)` which attempts scope-aware qualification via `tryQualify`.
146-
/// 2. Otherwise, attempt to build a fully qualified name with `qualifiedComponents` and look it up with `lookupType(fullyQualified:)`.
149+
/// Build the qualified name with `qualifiedComponents`, then attempt scope-aware qualification via `tryQualify`.
147150
///
148151
/// - Parameter type: The SwiftSyntax node representing a type appearance in source code.
149152
/// - Returns: The nominal declaration (enum/class/actor/struct) if found, otherwise nil.
150153
func resolve(_ type: TypeSyntax) -> TypeDecl? {
151-
if let id = type.as(IdentifierTypeSyntax.self) {
152-
return lookupType(for: id)
153-
}
154-
if let components = type.qualifiedComponents {
155-
return lookupType(fullyQualified: components)
154+
if let qualifiedName = tryQualify(type: type) {
155+
return lookupType(fullyQualified: qualifiedName)
156156
}
157157
return nil
158158
}
159159

160+
func resolveExtensionTarget(_ type: TypeSyntax) -> TypeDecl? {
161+
guard let qualifiedName = type.qualifiedComponents else {
162+
return nil
163+
}
164+
return lookupType(fullyQualified: qualifiedName)
165+
}
166+
160167
/// Resolves a type usage node to a type alias declaration
161168
///
162169
/// - Parameter type: The SwiftSyntax node representing a type appearance in source code.
163170
/// - Returns: The type alias declaration if found, otherwise nil.
164171
func resolveTypeAlias(_ type: TypeSyntax) -> TypeAliasDeclSyntax? {
165-
if let id = type.as(IdentifierTypeSyntax.self) {
166-
let qualifiedName = tryQualify(type: id)
172+
if let qualifiedName = tryQualify(type: type) {
167173
return typeAliasByQualifiedName[qualifiedName]
168174
}
169-
if let components = type.qualifiedComponents {
170-
return typeAliasByQualifiedName[components]
171-
}
172175
return nil
173176
}
174177

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
@JS class Library {
2+
@JS struct Shelf {
3+
@JS struct Divider {
4+
var slot: Int
5+
6+
@JS init(slot: Int) {
7+
self.slot = slot
8+
}
9+
}
10+
}
11+
12+
@JS init() {}
13+
}
14+
15+
extension Library {
16+
@JS func divider(_ value: Shelf.Divider) -> Shelf.Divider {
17+
value
18+
}
19+
}
20+
21+
@JS class Outer {
22+
@JS struct Inner {
23+
@JS struct Outer {
24+
@JS struct Inner {
25+
@JS init() {}
26+
}
27+
}
28+
29+
@JS init() {}
30+
}
31+
32+
@JS init() {}
33+
}
34+
35+
extension Outer.Inner {
36+
@JS func marker() -> Int { 1 }
37+
}
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
{
2+
"exported" : {
3+
"aliases" : [
4+
5+
],
6+
"classes" : [
7+
{
8+
"constructor" : {
9+
"abiName" : "bjs_Library_init",
10+
"effects" : {
11+
"isAsync" : false,
12+
"isStatic" : false,
13+
"isThrows" : false
14+
},
15+
"parameters" : [
16+
17+
]
18+
},
19+
"methods" : [
20+
{
21+
"abiName" : "bjs_Library_divider",
22+
"effects" : {
23+
"isAsync" : false,
24+
"isStatic" : false,
25+
"isThrows" : false
26+
},
27+
"name" : "divider",
28+
"parameters" : [
29+
{
30+
"label" : "_",
31+
"name" : "value",
32+
"type" : {
33+
"swiftStruct" : {
34+
"_0" : "Library.Shelf.Divider"
35+
}
36+
}
37+
}
38+
],
39+
"returnType" : {
40+
"swiftStruct" : {
41+
"_0" : "Library.Shelf.Divider"
42+
}
43+
}
44+
}
45+
],
46+
"name" : "Library",
47+
"properties" : [
48+
49+
],
50+
"swiftCallName" : "Library"
51+
},
52+
{
53+
"constructor" : {
54+
"abiName" : "bjs_Outer_init",
55+
"effects" : {
56+
"isAsync" : false,
57+
"isStatic" : false,
58+
"isThrows" : false
59+
},
60+
"parameters" : [
61+
62+
]
63+
},
64+
"methods" : [
65+
66+
],
67+
"name" : "Outer",
68+
"properties" : [
69+
70+
],
71+
"swiftCallName" : "Outer"
72+
}
73+
],
74+
"enums" : [
75+
76+
],
77+
"exposeToGlobal" : false,
78+
"functions" : [
79+
80+
],
81+
"protocols" : [
82+
83+
],
84+
"structs" : [
85+
{
86+
"methods" : [
87+
88+
],
89+
"name" : "Shelf",
90+
"namespace" : [
91+
"Library"
92+
],
93+
"properties" : [
94+
95+
],
96+
"swiftCallName" : "Library.Shelf"
97+
},
98+
{
99+
"constructor" : {
100+
"abiName" : "bjs_Library_Shelf_Divider_init",
101+
"effects" : {
102+
"isAsync" : false,
103+
"isStatic" : false,
104+
"isThrows" : false
105+
},
106+
"parameters" : [
107+
{
108+
"label" : "slot",
109+
"name" : "slot",
110+
"type" : {
111+
"integer" : {
112+
"_0" : {
113+
"isSigned" : true,
114+
"width" : "word"
115+
}
116+
}
117+
}
118+
}
119+
]
120+
},
121+
"methods" : [
122+
123+
],
124+
"name" : "Divider",
125+
"namespace" : [
126+
"Library",
127+
"Shelf"
128+
],
129+
"properties" : [
130+
{
131+
"isReadonly" : true,
132+
"isStatic" : false,
133+
"name" : "slot",
134+
"namespace" : [
135+
"Library",
136+
"Shelf"
137+
],
138+
"type" : {
139+
"integer" : {
140+
"_0" : {
141+
"isSigned" : true,
142+
"width" : "word"
143+
}
144+
}
145+
}
146+
}
147+
],
148+
"swiftCallName" : "Library.Shelf.Divider"
149+
},
150+
{
151+
"constructor" : {
152+
"abiName" : "bjs_Outer_Inner_init",
153+
"effects" : {
154+
"isAsync" : false,
155+
"isStatic" : false,
156+
"isThrows" : false
157+
},
158+
"parameters" : [
159+
160+
]
161+
},
162+
"methods" : [
163+
{
164+
"abiName" : "bjs_Outer_Inner_marker",
165+
"effects" : {
166+
"isAsync" : false,
167+
"isStatic" : false,
168+
"isThrows" : false
169+
},
170+
"name" : "marker",
171+
"parameters" : [
172+
173+
],
174+
"returnType" : {
175+
"integer" : {
176+
"_0" : {
177+
"isSigned" : true,
178+
"width" : "word"
179+
}
180+
}
181+
}
182+
}
183+
],
184+
"name" : "Inner",
185+
"namespace" : [
186+
"Outer"
187+
],
188+
"properties" : [
189+
190+
],
191+
"swiftCallName" : "Outer.Inner"
192+
},
193+
{
194+
"methods" : [
195+
196+
],
197+
"name" : "Outer",
198+
"namespace" : [
199+
"Outer",
200+
"Inner"
201+
],
202+
"properties" : [
203+
204+
],
205+
"swiftCallName" : "Outer.Inner.Outer"
206+
},
207+
{
208+
"constructor" : {
209+
"abiName" : "bjs_Outer_Inner_Outer_Inner_init",
210+
"effects" : {
211+
"isAsync" : false,
212+
"isStatic" : false,
213+
"isThrows" : false
214+
},
215+
"parameters" : [
216+
217+
]
218+
},
219+
"methods" : [
220+
221+
],
222+
"name" : "Inner",
223+
"namespace" : [
224+
"Outer",
225+
"Inner",
226+
"Outer"
227+
],
228+
"properties" : [
229+
230+
],
231+
"swiftCallName" : "Outer.Inner.Outer.Inner"
232+
}
233+
]
234+
},
235+
"moduleName" : "TestModule",
236+
"usedExternalModules" : [
237+
238+
]
239+
}

0 commit comments

Comments
 (0)