Skip to content

Commit 42a64e4

Browse files
committed
Use nominal type resolution to translate members of extensions
Use the new nominal type resolution functionality within jextract-swift to resolve extensions to their corresponding nominal types, so that members of those extensions show up as member of the resulting Java class.
1 parent b9a29c5 commit 42a64e4

File tree

4 files changed

+108
-17
lines changed

4 files changed

+108
-17
lines changed

Sources/JExtractSwift/NominalTypeResolution.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ import SwiftSyntax
1717
/// their extended nominal types and mapping type names to their full names.
1818
@_spi(Testing)
1919
public class NominalTypeResolution {
20-
/// A syntax node for a nominal type declaration.
21-
@_spi(Testing)
22-
public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax
23-
2420
/// Mapping from the syntax identifier for a given type declaration node,
2521
/// such as StructDeclSyntax, to the set of extensions of this particular
2622
/// type.
@@ -39,6 +35,10 @@ public class NominalTypeResolution {
3935
@_spi(Testing) public init() { }
4036
}
4137

38+
/// A syntax node for a nominal type declaration.
39+
@_spi(Testing)
40+
public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax
41+
4242
// MARK: Nominal type name resolution.
4343
extension NominalTypeResolution {
4444
/// Compute the fully-qualified name of the given nominal type node.
@@ -170,6 +170,7 @@ extension NominalTypeResolution {
170170
///
171171
/// Returns the list of extensions that could not be resolved.
172172
@_spi(Testing)
173+
@discardableResult
173174
public func bindExtensions() -> [ExtensionDeclSyntax] {
174175
while !unresolvedExtensions.isEmpty {
175176
// Try to resolve all of the unresolved extensions.

Sources/JExtractSwift/Swift2JavaTranslator.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public final class Swift2JavaTranslator {
4141
/// type representation.
4242
public var importedTypes: [String: ImportedNominalType] = [:]
4343

44+
let nominalResolution: NominalTypeResolution = NominalTypeResolution()
45+
4446
public init(
4547
javaPackage: String,
4648
swiftModuleName: String
@@ -82,6 +84,10 @@ extension Swift2JavaTranslator {
8284

8385
let sourceFileSyntax = Parser.parse(source: text)
8486

87+
// Find all of the types and extensions, then bind the extensions.
88+
nominalResolution.addSourceFile(sourceFileSyntax)
89+
nominalResolution.bindExtensions()
90+
8591
let visitor = Swift2JavaVisitor(
8692
moduleName: self.swiftModuleName,
8793
targetJavaPackage: self.javaPackage,

Sources/JExtractSwift/Swift2JavaVisitor.swift

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
2525
/// store this along with type names as we import them.
2626
let targetJavaPackage: String
2727

28+
/// The current type name as a nested name like A.B.C.
2829
var currentTypeName: String? = nil
2930

3031
var log: Logger { translator.log }
@@ -37,27 +38,46 @@ final class Swift2JavaVisitor: SyntaxVisitor {
3738
super.init(viewMode: .all)
3839
}
3940

40-
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
41-
guard node.shouldImport(log: log) else {
42-
return .skipChildren
41+
/// Try to resolve the given nominal type node into its imported
42+
/// representation.
43+
func resolveNominalType(
44+
_ nominal: some DeclGroupSyntax & NamedDeclSyntax,
45+
kind: NominalTypeKind
46+
) -> ImportedNominalType? {
47+
if !nominal.shouldImport(log: log) {
48+
return nil
49+
}
50+
51+
guard let fullName = translator.nominalResolution.fullyQualifiedName(of: nominal) else {
52+
return nil
53+
}
54+
55+
if let alreadyImported = translator.importedTypes[fullName] {
56+
return alreadyImported
4357
}
4458

45-
log.info("Import: \(node.kind) \(node.name)")
46-
let typeName = node.name.text
47-
currentTypeName = typeName
48-
translator.importedTypes[typeName] = ImportedNominalType(
49-
// TODO: support nested classes (parent name here)
59+
let importedNominal = ImportedNominalType(
5060
name: ImportedTypeName(
51-
swiftTypeName: typeName,
61+
swiftTypeName: fullName,
5262
javaType: .class(
5363
package: targetJavaPackage,
54-
name: typeName
64+
name: fullName
5565
),
56-
swiftMangledName: node.mangledNameFromComment
66+
swiftMangledName: nominal.mangledNameFromComment
5767
),
58-
kind: .class
68+
kind: kind
5969
)
6070

71+
translator.importedTypes[fullName] = importedNominal
72+
return importedNominal
73+
}
74+
75+
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
76+
guard let importedNominalType = resolveNominalType(node, kind: .class) else {
77+
return .skipChildren
78+
}
79+
80+
currentTypeName = importedNominalType.name.swiftTypeName
6181
return .visitChildren
6282
}
6383

@@ -68,6 +88,24 @@ final class Swift2JavaVisitor: SyntaxVisitor {
6888
}
6989
}
7090

91+
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
92+
// Resolve the extended type of the extension as an imported nominal, and
93+
// recurse if we found it.
94+
guard let nominal = translator.nominalResolution.extendedType(of: node),
95+
let importedNominalType = resolveNominalType(nominal, kind: .class) else {
96+
return .skipChildren
97+
}
98+
99+
currentTypeName = importedNominalType.name.swiftTypeName
100+
return .visitChildren
101+
}
102+
103+
override func visitPost(_ node: ExtensionDeclSyntax) {
104+
if currentTypeName != nil {
105+
currentTypeName = nil
106+
}
107+
}
108+
71109
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
72110
guard node.shouldImport(log: log) else {
73111
return .skipChildren
@@ -182,7 +220,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
182220
}
183221
}
184222

185-
extension ClassDeclSyntax {
223+
extension DeclGroupSyntax where Self: NamedDeclSyntax {
186224
func shouldImport(log: Logger) -> Bool {
187225
guard (accessControlModifiers.first { $0.isPublic }) != nil else {
188226
log.trace("Cannot import \(self.name) because: is not public")

Tests/JExtractSwiftTests/FuncImportTests.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ final class MethodImportTests: XCTestCase {
3737
// MANGLED NAME: $s14MySwiftLibrary23globalTakeLongIntString1l3i321sys5Int64V_s5Int32VSStF
3838
public func globalTakeIntLongString(i32: Int32, l: Int64, s: String)
3939
40+
extension MySwiftClass {
41+
// MANGLED NAME: $s14MySwiftLibrary0aB5ClassC22helloMemberFunctionInExtension
42+
public func helloMemberInExtension()
43+
}
44+
4045
// MANGLED NAME: $s14MySwiftLibrary0aB5ClassCMa
4146
public class MySwiftClass {
4247
// MANGLED NAME: $s14MySwiftLibrary0aB5ClassC3len3capACSi_SitcfC
@@ -213,6 +218,47 @@ final class MethodImportTests: XCTestCase {
213218
)
214219
}
215220

221+
func test_method_class_helloMemberInExtension_self_memorySegment() async throws {
222+
let st = Swift2JavaTranslator(
223+
javaPackage: "com.example.swift",
224+
swiftModuleName: "__FakeModule"
225+
)
226+
st.log.logLevel = .trace
227+
228+
try await st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile)
229+
230+
let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first {
231+
$0.baseIdentifier == "helloMemberInExtension"
232+
}!
233+
234+
let output = CodePrinter.toString { printer in
235+
st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: .memorySegment)
236+
}
237+
238+
assertOutput(
239+
output,
240+
expected:
241+
"""
242+
/**
243+
* {@snippet lang=swift :
244+
* public func helloMemberInExtension()
245+
* }
246+
*/
247+
public static void helloMemberInExtension(java.lang.foreign.MemorySegment self$) {
248+
var mh$ = helloMemberInExtension.HANDLE;
249+
try {
250+
if (TRACE_DOWNCALLS) {
251+
traceDowncall(self$);
252+
}
253+
mh$.invokeExact(self$);
254+
} catch (Throwable ex$) {
255+
throw new AssertionError("should not reach here", ex$);
256+
}
257+
}
258+
"""
259+
)
260+
}
261+
216262
func test_method_class_helloMemberFunction_self_wrapper() async throws {
217263
let st = Swift2JavaTranslator(
218264
javaPackage: "com.example.swift",

0 commit comments

Comments
 (0)