Skip to content

Commit cafcc1d

Browse files
authored
[jextract/jni] Add support for importing nested types (#429)
1 parent cee5e02 commit cafcc1d

File tree

11 files changed

+269
-16
lines changed

11 files changed

+269
-16
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import SwiftJava
16+
17+
public class A {
18+
public init() {}
19+
20+
public class B {
21+
public init() {}
22+
23+
public struct C {
24+
public init() {}
25+
26+
public func g(a: A, b: B, bbc: BB.C) {}
27+
}
28+
}
29+
30+
public class BB {
31+
public init() {}
32+
33+
public struct C {
34+
public init() {}
35+
}
36+
}
37+
38+
public func f(a: A, b: A.B, c: A.B.C, bb: BB, bbc: BB.C) {}
39+
}
40+
41+
public enum NestedEnum {
42+
case one(OneStruct)
43+
44+
public struct OneStruct {
45+
public init() {}
46+
}
47+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
package com.example.swift;
16+
17+
import org.junit.jupiter.api.Test;
18+
import org.swift.swiftkit.core.SwiftArena;
19+
20+
import static org.junit.jupiter.api.Assertions.*;
21+
22+
public class NestedTypesTest {
23+
@Test
24+
void testClassesAndStructs() {
25+
try (var arena = SwiftArena.ofConfined()) {
26+
var a = A.init(arena);
27+
var b = A.B.init(arena);
28+
var c = A.B.C.init(arena);
29+
var bb = A.BB.init(arena);
30+
var abbc = A.BB.C.init(arena);
31+
32+
a.f(a, b, c, bb, abbc);
33+
c.g(a, b, abbc);
34+
}
35+
}
36+
37+
@Test
38+
void testStructInEnum() {
39+
try (var arena = SwiftArena.ofConfined()) {
40+
var obj = NestedEnum.one(NestedEnum.OneStruct.init(arena), arena);
41+
var one = obj.getAsOne(arena);
42+
assertTrue(one.isPresent());
43+
}
44+
}
45+
}

Sources/JExtractSwiftLib/ImportedDecls.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ package final class ImportedNominalType: ImportedDecl {
4141
package var variables: [ImportedFunc] = []
4242
package var cases: [ImportedEnumCase] = []
4343
var inheritedTypes: [SwiftType]
44+
package var parent: SwiftNominalTypeDeclaration?
4445

4546
init(swiftNominal: SwiftNominalTypeDeclaration, lookupContext: SwiftTypeLookupContext) throws {
4647
self.swiftNominal = swiftNominal
4748
self.inheritedTypes = swiftNominal.inheritanceTypes?.compactMap {
4849
try? SwiftType($0.type, lookupContext: lookupContext)
4950
} ?? []
51+
self.parent = swiftNominal.parent
5052
}
5153

5254
var swiftType: SwiftType {

Sources/JExtractSwiftLib/JNI/JNICaching.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414

1515
enum JNICaching {
1616
static func cacheName(for type: ImportedNominalType) -> String {
17-
cacheName(for: type.swiftNominal.name)
17+
cacheName(for: type.swiftNominal.qualifiedName)
1818
}
1919

2020
static func cacheName(for type: SwiftNominalType) -> String {
21-
cacheName(for: type.nominalTypeDecl.name)
21+
cacheName(for: type.nominalTypeDecl.qualifiedName)
2222
}
2323

24-
private static func cacheName(for name: String) -> String {
25-
"_JNI_\(name)"
24+
private static func cacheName(for qualifiedName: String) -> String {
25+
"_JNI_\(qualifiedName.replacingOccurrences(of: ".", with: "_"))"
2626
}
2727

2828
static func cacheMemberName(for enumCase: ImportedEnumCase) -> String {

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ extension JNISwift2JavaGenerator {
4040
package func writeExportedJavaSources(_ printer: inout CodePrinter) throws {
4141
let importedTypes = analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key })
4242

43-
for (_, ty) in importedTypes {
43+
// Each parent type goes into its own file
44+
// any nested types are printed inside the body as `static class`
45+
for (_, ty) in importedTypes.filter({ _, type in type.parent == nil }) {
4446
let filename = "\(ty.swiftNominal.name).java"
4547
logger.debug("Printing contents: \(filename)")
4648
printImportedNominal(&printer, ty)
@@ -145,6 +147,15 @@ extension JNISwift2JavaGenerator {
145147
"""
146148
)
147149

150+
let nestedTypes = self.analysis.importedTypes.filter { _, type in
151+
type.parent == decl.swiftNominal
152+
}
153+
154+
for nestedType in nestedTypes {
155+
printConcreteType(&printer, nestedType.value)
156+
printer.println()
157+
}
158+
148159
printer.print(
149160
"""
150161
/**
@@ -255,13 +266,18 @@ extension JNISwift2JavaGenerator {
255266
if decl.swiftNominal.isSendable {
256267
printer.print("@ThreadSafe // Sendable")
257268
}
269+
var modifiers = ["public"]
270+
if decl.parent != nil {
271+
modifiers.append("static")
272+
}
273+
modifiers.append("final")
258274
var implements = ["JNISwiftInstance"]
259275
implements += decl.inheritedTypes
260276
.compactMap(\.asNominalTypeDeclaration)
261277
.filter { $0.kind == .protocol }
262278
.map(\.name)
263279
let implementsClause = implements.joined(separator: ", ")
264-
printer.printBraceBlock("public final class \(decl.swiftNominal.name) implements \(implementsClause)") { printer in
280+
printer.printBraceBlock("\(modifiers.joined(separator: " ")) class \(decl.swiftNominal.name) implements \(implementsClause)") { printer in
265281
body(&printer)
266282
}
267283
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,7 @@ extension JNISwift2JavaGenerator {
307307
genericParameters: [SwiftGenericParameterDeclaration],
308308
genericRequirements: [SwiftGenericRequirement]
309309
) throws -> [TranslatedParameter] {
310-
try parameters.enumerated().map {
311-
idx,
312-
param in
310+
try parameters.enumerated().map { idx, param in
313311
let parameterName = param.name ?? "arg\(idx)"
314312
return try translateParameter(
315313
swiftType: param.type,
@@ -373,7 +371,7 @@ extension JNISwift2JavaGenerator {
373371

374372
switch swiftType {
375373
case .nominal(let nominalType):
376-
let nominalTypeName = nominalType.nominalTypeDecl.name
374+
let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName
377375

378376
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
379377
switch knownType {

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ extension JNISwift2JavaGenerator {
430430
let cName =
431431
"Java_"
432432
+ self.javaPackage.replacingOccurrences(of: ".", with: "_")
433-
+ "_\(parentName.escapedJNIIdentifier)_"
433+
+ "_\(parentName.replacingOccurrences(of: ".", with: "$").escapedJNIIdentifier)_"
434434
+ javaMethodName.escapedJNIIdentifier
435435
+ "__"
436436
+ jniSignature.escapedJNIIdentifier
@@ -474,7 +474,7 @@ extension JNISwift2JavaGenerator {
474474
printCDecl(
475475
&printer,
476476
javaMethodName: "$typeMetadataAddressDowncall",
477-
parentName: type.swiftNominal.name,
477+
parentName: type.swiftNominal.qualifiedName,
478478
parameters: [],
479479
resultType: .long
480480
) { printer in
@@ -493,7 +493,7 @@ extension JNISwift2JavaGenerator {
493493
printCDecl(
494494
&printer,
495495
javaMethodName: "$destroy",
496-
parentName: type.swiftNominal.name,
496+
parentName: type.swiftNominal.qualifiedName,
497497
parameters: [
498498
selfPointerParam
499499
],

Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,4 @@ extension SwiftModuleSymbolTable {
5656
/// Names of modules which are alternative for currently checked module.
5757
let moduleNames: Set<String>
5858
}
59-
}
59+
}

Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,15 @@ class SwiftTypeLookupContext {
119119

120120
/// Create a nominal type declaration instance for the specified syntax node.
121121
private func nominalTypeDeclaration(for node: NominalTypeDeclSyntaxNode, sourceFilePath: String) throws -> SwiftNominalTypeDeclaration {
122-
SwiftNominalTypeDeclaration(
122+
123+
if let symbolTableDeclaration = self.symbolTable.lookupType(
124+
node.name.text,
125+
parent: try parentTypeDecl(for: node)
126+
) {
127+
return symbolTableDeclaration
128+
}
129+
130+
return SwiftNominalTypeDeclaration(
123131
sourceFilePath: sourceFilePath,
124132
moduleName: self.symbolTable.moduleName,
125133
parent: try parentTypeDecl(for: node),

Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S
8686
| Subscripts: `subscript()` |||
8787
| Equatable |||
8888
| Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 ||
89-
| Nested types: `struct Hello { struct World {} }` || |
89+
| Nested types: `struct Hello { struct World {} }` || |
9090
| Inheritance: `class Caplin: Capybara` |||
9191
| Non-escaping `Void` closures: `func callMe(maybe: () -> ())` |||
9292
| Non-escaping closures with primitive arguments/results: `func callMe(maybe: (Int) -> (Double))` |||

0 commit comments

Comments
 (0)