Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import SwiftJava

public class A {
public init() {}

public class B {
public init() {}

public struct C {
public init() {}

public func g(a: A, b: B, bbc: BB.C) {}
}
}

public class BB {
public init() {}

public struct C {
public init() {}
}
}

public func f(a: A, b: A.B, c: A.B.C, bb: BB, bbc: BB.C) {}
}

public enum NestedEnum {
case one(OneStruct)

public struct OneStruct {
public init() {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

package com.example.swift;

import org.junit.jupiter.api.Test;
import org.swift.swiftkit.core.SwiftArena;

import static org.junit.jupiter.api.Assertions.*;

public class NestedTypesTest {
@Test
void testClassesAndStructs() {
try (var arena = SwiftArena.ofConfined()) {
var a = A.init(arena);
var b = A.B.init(arena);
var c = A.B.C.init(arena);
var bb = A.BB.init(arena);
var abbc = A.BB.C.init(arena);

a.f(a, b, c, bb, abbc);
c.g(a, b, abbc);
}
}

@Test
void testStructInEnum() {
try (var arena = SwiftArena.ofConfined()) {
var obj = NestedEnum.one(NestedEnum.OneStruct.init(arena), arena);
var one = obj.getAsOne(arena);
assertTrue(one.isPresent());
}
}
}
2 changes: 2 additions & 0 deletions Sources/JExtractSwiftLib/ImportedDecls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ package final class ImportedNominalType: ImportedDecl {
package var variables: [ImportedFunc] = []
package var cases: [ImportedEnumCase] = []
var inheritedTypes: [SwiftType]
package var parent: SwiftNominalTypeDeclaration?

init(swiftNominal: SwiftNominalTypeDeclaration, lookupContext: SwiftTypeLookupContext) throws {
self.swiftNominal = swiftNominal
self.inheritedTypes = swiftNominal.inheritanceTypes?.compactMap {
try? SwiftType($0.type, lookupContext: lookupContext)
} ?? []
self.parent = swiftNominal.parent
}

var swiftType: SwiftType {
Expand Down
8 changes: 4 additions & 4 deletions Sources/JExtractSwiftLib/JNI/JNICaching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@

enum JNICaching {
static func cacheName(for type: ImportedNominalType) -> String {
cacheName(for: type.swiftNominal.name)
cacheName(for: type.swiftNominal.qualifiedName)
}

static func cacheName(for type: SwiftNominalType) -> String {
cacheName(for: type.nominalTypeDecl.name)
cacheName(for: type.nominalTypeDecl.qualifiedName)
}

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

static func cacheMemberName(for enumCase: ImportedEnumCase) -> String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ extension JNISwift2JavaGenerator {
package func writeExportedJavaSources(_ printer: inout CodePrinter) throws {
let importedTypes = analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key })

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

let nestedTypes = self.analysis.importedTypes.filter { _, type in
type.parent == decl.swiftNominal
}

for nestedType in nestedTypes {
printConcreteType(&printer, nestedType.value)
printer.println()
}

printer.print(
"""
/**
Expand Down Expand Up @@ -255,13 +266,18 @@ extension JNISwift2JavaGenerator {
if decl.swiftNominal.isSendable {
printer.print("@ThreadSafe // Sendable")
}
var modifiers = ["public"]
if decl.parent != nil {
modifiers.append("static")
}
modifiers.append(contentsOf: ["final", "class"])
var implements = ["JNISwiftInstance"]
implements += decl.inheritedTypes
.compactMap(\.asNominalTypeDeclaration)
.filter { $0.kind == .protocol }
.map(\.name)
let implementsClause = implements.joined(separator: ", ")
printer.printBraceBlock("public final class \(decl.swiftNominal.name) implements \(implementsClause)") { printer in
printer.printBraceBlock("\(modifiers.joined(separator: " ")) \(decl.swiftNominal.name) implements \(implementsClause)") { printer in
body(&printer)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,7 @@ extension JNISwift2JavaGenerator {
genericParameters: [SwiftGenericParameterDeclaration],
genericRequirements: [SwiftGenericRequirement]
) throws -> [TranslatedParameter] {
try parameters.enumerated().map {
idx,
param in
try parameters.enumerated().map { idx, param in
let parameterName = param.name ?? "arg\(idx)"
return try translateParameter(
swiftType: param.type,
Expand Down Expand Up @@ -373,7 +371,7 @@ extension JNISwift2JavaGenerator {

switch swiftType {
case .nominal(let nominalType):
let nominalTypeName = nominalType.nominalTypeDecl.name
let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName

if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
switch knownType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ extension JNISwift2JavaGenerator {
let cName =
"Java_"
+ self.javaPackage.replacingOccurrences(of: ".", with: "_")
+ "_\(parentName.escapedJNIIdentifier)_"
+ "_\(parentName.replacingOccurrences(of: ".", with: "$").escapedJNIIdentifier)_"
+ javaMethodName.escapedJNIIdentifier
+ "__"
+ jniSignature.escapedJNIIdentifier
Expand Down Expand Up @@ -474,7 +474,7 @@ extension JNISwift2JavaGenerator {
printCDecl(
&printer,
javaMethodName: "$typeMetadataAddressDowncall",
parentName: type.swiftNominal.name,
parentName: type.swiftNominal.qualifiedName,
parameters: [],
resultType: .long
) { printer in
Expand All @@ -493,7 +493,7 @@ extension JNISwift2JavaGenerator {
printCDecl(
&printer,
javaMethodName: "$destroy",
parentName: type.swiftNominal.name,
parentName: type.swiftNominal.qualifiedName,
parameters: [
selfPointerParam
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@ extension SwiftModuleSymbolTable {
/// Names of modules which are alternative for currently checked module.
let moduleNames: Set<String>
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,15 @@ class SwiftTypeLookupContext {

/// Create a nominal type declaration instance for the specified syntax node.
private func nominalTypeDeclaration(for node: NominalTypeDeclSyntaxNode, sourceFilePath: String) throws -> SwiftNominalTypeDeclaration {
SwiftNominalTypeDeclaration(

if let symbolTableDeclaration = self.symbolTable.lookupType(
node.name.text,
parent: try parentTypeDecl(for: node)
) {
return symbolTableDeclaration
}

return SwiftNominalTypeDeclaration(
sourceFilePath: sourceFilePath,
moduleName: self.symbolTable.moduleName,
parent: try parentTypeDecl(for: node),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S
| Subscripts: `subscript()` | ❌ | ❌ |
| Equatable | ❌ | ❌ |
| Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ |
| Nested types: `struct Hello { struct World {} }` | ❌ | |
| Nested types: `struct Hello { struct World {} }` | ❌ | |
| Inheritance: `class Caplin: Capybara` | ❌ | ❌ |
| Non-escaping `Void` closures: `func callMe(maybe: () -> ())` | ✅ | ✅ |
| Non-escaping closures with primitive arguments/results: `func callMe(maybe: (Int) -> (Double))` | ✅ | ✅ |
Expand Down
137 changes: 137 additions & 0 deletions Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import JExtractSwiftLib
import Testing

@Suite
struct JNINestedTypesTests {
let source1 = """
public class A {
public class B {
public func g(c: C) {}

public struct C {
public func h(b: B) {}
}
}
}

public func f(a: A, b: A.B, c: A.B.C) {}
"""

@Test("Import: class and struct A.B.C (Java)")
func nestedClassesAndStructs_java() throws {
try assertOutput(
input: source1,
.jni, .java,
detectChunkByInitialLines: 1,
expectedChunks: [
"""
public final class A implements JNISwiftInstance {
...
public static final class B implements JNISwiftInstance {
...
public static final class C implements JNISwiftInstance {
...
public void h(A.B b) {
...
}
...
public void g(A.B.C c) {
...
}
...
}
""",
"""
public static void f(A a, A.B b, A.B.C c) {
...
}
...
"""
]
)
}

@Test("Import: class and struct A.B.C (Swift)")
func nestedClassesAndStructs_swift() throws {
try assertOutput(
input: source1,
.jni, .swift,
detectChunkByInitialLines: 1,
expectedChunks: [
"""
@_cdecl("Java_com_example_swift_A__00024destroy__J")
func Java_com_example_swift_A__00024destroy__J(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, selfPointer: jlong) {
...
}
""",
"""
@_cdecl("Java_com_example_swift_A_00024B__00024destroy__J")
func Java_com_example_swift_A_00024B__00024destroy__J(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, selfPointer: jlong) {
...
}
""",
"""
@_cdecl("Java_com_example_swift_A_00024B__00024destroy__J")
func Java_com_example_swift_A_00024B__00024destroy__J(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, selfPointer: jlong) {
...
}
""",
"""
@_cdecl("Java_com_example_swift_A_00024B_00024C__00024h__JJ")
func Java_com_example_swift_A_00024B_00024C__00024h__JJ(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, b: jlong, self: jlong) {
...
}
"""
]
)
}

@Test("Import: nested in enum")
func nestedEnums_java() throws {
try assertOutput(
input: """
public enum MyError {
case text(TextMessage)

public struct TextMessage {}
}

public func f(text: MyError.TextMessage) {}
""",
.jni, .java,
detectChunkByInitialLines: 1,
expectedChunks: [
"""
public final class MyError implements JNISwiftInstance {
...
public static final class TextMessage implements JNISwiftInstance {
...
}
...
public static MyError text(MyError.TextMessage arg0, SwiftArena swiftArena$) {
...
}
""",
"""
public static void f(MyError.TextMessage text) {
...
}
"""
]
)
}
}
Loading