Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5caa74f
Add test code for generic type
sidepelican Feb 13, 2026
d46019f
Implement type parameter translations
sidepelican Feb 16, 2026
f935933
Keep type parameter names short in arguments
sidepelican Feb 16, 2026
4ff9e07
Changed to only handle metatypes for Self
sidepelican Feb 16, 2026
86ce9ac
Add test case for swift side generated code
sidepelican Feb 16, 2026
49fbd08
Remove unused step
sidepelican Feb 16, 2026
32d9978
Print opener thunk
sidepelican Feb 18, 2026
55465d1
Indirect generic return value
sidepelican Feb 19, 2026
a120d0b
Skip generating typeMetadataAddressDowncall if the type is generic
sidepelican Feb 19, 2026
0231d72
Handling the destroy function
sidepelican Feb 19, 2026
d7253d2
A bit refactoring
sidepelican Feb 20, 2026
7a5a7c9
apply formatter
sidepelican Feb 20, 2026
e5f65f3
Refactor translateSelfTypeParameter
sidepelican Feb 20, 2026
686b343
Add example for generic type property
sidepelican Feb 20, 2026
00373a7
Fix some parameter name for test
sidepelican Feb 20, 2026
f4f27cc
Fix static method skipping way
sidepelican Feb 20, 2026
ea59102
Merge branch 'main' into generic_call
sidepelican Feb 24, 2026
b5856c3
Fix typo
sidepelican Feb 24, 2026
66018b3
Add enum support
sidepelican Feb 25, 2026
d4cd58e
Update supported feature list
sidepelican Feb 25, 2026
27a7a9a
Fix copyright year
sidepelican Feb 25, 2026
10f9f2a
includes selfType$ in CallTraces.traceDowncall
sidepelican Feb 25, 2026
91bb797
Add document to OutSwiftGenericInstance
sidepelican Feb 25, 2026
016766b
Add detailed document about generic types
sidepelican Feb 25, 2026
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,49 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 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
//
//===----------------------------------------------------------------------===//

public struct MyID<T> {
public var rawValue: T
public init(_ rawValue: T) {
self.rawValue = rawValue
}
public var description: String {
"\(rawValue)"
}
}

public func makeIntID(_ value: Int) -> MyID<Int> {
MyID(value)
}

public func makeStringID(_ value: String) -> MyID<String> {
MyID(value)
}

public struct MyEntity {
public var id: MyID<Int>
public var name: String
public init(id: MyID<Int>, name: String) {
self.id = id
self.name = name
}
}

public enum GenericEnum<T> {
case foo
case bar
}

public func makeIntGenericEnum() -> GenericEnum<Int> {
if Bool.random() { return .foo } else { return .bar }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 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 GenericTypeTest {
@Test
void returnsGenericType() {
try (var arena = SwiftArena.ofConfined()) {
MyID stringId = MySwiftLibrary.makeStringID("Java", arena);
assertEquals("Java", stringId.getDescription());

MyID intId = MySwiftLibrary.makeIntID(42, arena);
assertEquals("42", intId.getDescription());
}
}

@Test
void genericTypeProperty() {
try (var arena = SwiftArena.ofConfined()) {
MyID intId = MySwiftLibrary.makeIntID(42, arena);
MyEntity entity = MyEntity.init(intId, "name", arena);
assertEquals("42", entity.getId(arena).getDescription());
}
}

@Test
void genericEnum() {
try (var arena = SwiftArena.ofConfined()) {
GenericEnum value = MySwiftLibrary.makeIntGenericEnum(arena);
switch (value.getCase()) {
case GenericEnum.Foo _ -> assertTrue(value.getAsFoo().isPresent());
case GenericEnum.Bar _ -> assertTrue(value.getAsBar().isPresent());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,33 @@ extension JNISwift2JavaGenerator {
* @param selfPointer a pointer to the memory containing the value
* @param swiftArena the arena this object belongs to. When the arena goes out of scope, this value is destroyed.
*/
private \(decl.swiftNominal.name)(long selfPointer, SwiftArena swiftArena) {
SwiftObjects.requireNonZero(selfPointer, "selfPointer");
this.selfPointer = selfPointer;
"""
)
var swiftPointerParams = ["selfPointer"]
if decl.swiftNominal.isGeneric {
swiftPointerParams.append("selfTypePointer")
}
let swiftPointerArg = swiftPointerParams.map { "long \($0)" }.joined(separator: ", ")
printer.printBraceBlock("private \(decl.swiftNominal.name)(\(swiftPointerArg), SwiftArena swiftArena)") { printer in
for param in swiftPointerParams {
printer.print(
"""
SwiftObjects.requireNonZero(\(param), "\(param)");
this.\(param) = \(param);
"""
)
}
printer.print(
"""

// Only register once we have fully initialized the object since this will need the object pointer.
swiftArena.register(this);
}

"""
)
}
printer.println()
printer.print(
"""
/**
* Assume that the passed {@code long} represents a memory address of a {@link \(decl.swiftNominal.name)}.
* <p/>
Expand All @@ -216,12 +235,12 @@ extension JNISwift2JavaGenerator {
* <li>This operation does not copy, or retain, the pointed at pointer, so its lifetime must be ensured manually to be valid when wrapping.</li>
* </ul>
*/
public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) {
return new \(decl.swiftNominal.name)(selfPointer, swiftArena);
public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(\(swiftPointerArg), SwiftArena swiftArena) {
return new \(decl.swiftNominal.name)(\(swiftPointerParams.joined(separator: ", ")), swiftArena);
}

public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(long selfPointer) {
return new \(decl.swiftNominal.name)(selfPointer, SwiftMemoryManagement.DEFAULT_SWIFT_JAVA_AUTO_ARENA);
public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(\(swiftPointerArg)) {
return new \(decl.swiftNominal.name)(\(swiftPointerParams.joined(separator: ", ")), SwiftMemoryManagement.DEFAULT_SWIFT_JAVA_AUTO_ARENA);
}
"""
)
Expand All @@ -245,6 +264,11 @@ extension JNISwift2JavaGenerator {
"""
)

if decl.swiftNominal.isGeneric {
printer.print("/** Pointer to the metatype of Self */")
printer.print("private final long selfTypePointer;")
}

printer.println()

if decl.swiftNominal.kind == .enum {
Expand Down Expand Up @@ -367,10 +391,17 @@ extension JNISwift2JavaGenerator {
}

// TODO: Consider whether all of these "utility" functions can be printed using our existing printing logic.
printer.printBraceBlock("public Discriminator getDiscriminator()") { printer in
printer.print("return Discriminator.values()[$getDiscriminator(this.$memoryAddress())];")
if decl.swiftNominal.isGeneric {
printer.printBraceBlock("public Discriminator getDiscriminator()") { printer in
printer.print("return Discriminator.values()[$getDiscriminator(this.$memoryAddress(), this.$typeMetadataAddress())];")
}
printer.print("private static native int $getDiscriminator(long self, long selfType);")
} else {
printer.printBraceBlock("public Discriminator getDiscriminator()") { printer in
printer.print("return Discriminator.values()[$getDiscriminator(this.$memoryAddress())];")
}
printer.print("private static native int $getDiscriminator(long self);")
}
printer.print("private static native int $getDiscriminator(long self);")
}

private func printEnumCaseInterface(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
Expand Down Expand Up @@ -399,6 +430,11 @@ extension JNISwift2JavaGenerator {
}

private func printEnumStaticInitializers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
if !decl.cases.isEmpty && decl.swiftNominal.isGeneric {
self.logger.debug("Skipping generic static initializers in '\(decl.swiftNominal.name)'")
return
}

for enumCase in decl.cases {
printFunctionDowncallMethods(&printer, enumCase.caseFunction)
}
Expand Down Expand Up @@ -605,6 +641,9 @@ extension JNISwift2JavaGenerator {
if let selfParameter = nativeSignature.selfParameter?.parameters {
parameters += selfParameter
}
if let selfTypeParameter = nativeSignature.selfTypeParameter?.parameters {
parameters += selfTypeParameter
}
parameters += nativeSignature.result.outParameters

let renderedParameters = parameters.map { javaParameter in
Expand Down Expand Up @@ -633,6 +672,12 @@ extension JNISwift2JavaGenerator {
arguments.append(lowered)
}

// 'Self' metatype.
if let selfTypeParameter = translatedFunctionSignature.selfTypeParameter {
let lowered = selfTypeParameter.conversion.render(&printer, "this")
arguments.append(lowered)
}

// Indirect return receivers
for outParameter in translatedFunctionSignature.resultType.outParameters {
printer.print(
Expand All @@ -657,51 +702,86 @@ extension JNISwift2JavaGenerator {
}

private func printTypeMetadataAddressFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) {
printer.print("private static native long $typeMetadataAddressDowncall();")

let funcName = "$typeMetadataAddress"
printer.print("@Override")
printer.printBraceBlock("public long $typeMetadataAddress()") { printer in
printer.print(
"""
long self$ = this.$memoryAddress();
if (CallTraces.TRACE_DOWNCALLS) {
CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)",
"this", this,
"self", self$);
}
return \(type.swiftNominal.name).$typeMetadataAddressDowncall();
"""
)
if type.swiftNominal.isGeneric {
printer.print("@Override")
printer.printBraceBlock("public long $typeMetadataAddress()") { printer in
printer.print("return this.selfTypePointer;")
}
} else {
printer.print("private static native long $typeMetadataAddressDowncall();")

let funcName = "$typeMetadataAddress"
printer.print("@Override")
printer.printBraceBlock("public long $typeMetadataAddress()") { printer in
printer.print(
"""
long self$ = this.$memoryAddress();
if (CallTraces.TRACE_DOWNCALLS) {
CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)",
"this", this,
"self", self$);
}
"""
)
printer.print("return \(type.swiftNominal.name).$typeMetadataAddressDowncall();")
}
}
}

/// Prints the destroy function for a `JNISwiftInstance`
private func printDestroyFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) {
printer.print("private static native void $destroy(long selfPointer);")
let isGeneric = type.swiftNominal.isGeneric
if isGeneric {
printer.print("private static native void $destroy(long selfPointer, long selfType);")
} else {
printer.print("private static native void $destroy(long selfPointer);")
}

let funcName = "$createDestroyFunction"
printer.print("@Override")
printer.printBraceBlock("public Runnable \(funcName)()") { printer in
printer.print(
"""
long self$ = this.$memoryAddress();
if (CallTraces.TRACE_DOWNCALLS) {
CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)",
"this", this,
"self", self$);
}
return new Runnable() {
@Override
public void run() {
if (CallTraces.TRACE_DOWNCALLS) {
CallTraces.traceDowncall("\(type.swiftNominal.name).$destroy", "self", self$);
printer.print("long self$ = this.$memoryAddress();")
if isGeneric {
printer.print(
"""
long selfType$ = this.$typeMetadataAddress();
if (CallTraces.TRACE_DOWNCALLS) {
CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)",
"this", this,
"self", self$,
"selfType", selfType$);
}
return new Runnable() {
@Override
public void run() {
if (CallTraces.TRACE_DOWNCALLS) {
CallTraces.traceDowncall("\(type.swiftNominal.name).$destroy", "self", self$, "selfType", selfType$);
}
\(type.swiftNominal.name).$destroy(self$, selfType$);
}
\(type.swiftNominal.name).$destroy(self$);
};
"""
)
} else {
printer.print(
"""
if (CallTraces.TRACE_DOWNCALLS) {
CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)",
"this", this,
"self", self$);
}
};
"""
)
return new Runnable() {
@Override
public void run() {
if (CallTraces.TRACE_DOWNCALLS) {
CallTraces.traceDowncall("\(type.swiftNominal.name).$destroy", "self", self$);
}
\(type.swiftNominal.name).$destroy(self$);
}
};
"""
)
}
}
}

Expand Down
Loading