Skip to content

[JExtract] Import initializers as static methods #245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -45,7 +45,7 @@ public void beforeALl() {
System.setProperty("jextract.trace.downcalls", "false");

arena = SwiftArena.ofConfined();
obj = new MySwiftClass(1, 2, arena);
obj = MySwiftClass.init(1, 2, arena);
}

@TearDown(Level.Trial)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ static void examples() {

// Example of using an arena; MyClass.deinit is run at end of scope
try (var arena = SwiftArena.ofConfined()) {
MySwiftClass obj = new MySwiftClass(2222, 7777, arena);
MySwiftClass obj = MySwiftClass.init(2222, 7777, arena);

// just checking retains/releases work
SwiftKit.retain(obj);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ void checkPaths(Throwable throwable) {
@Test
void test_MySwiftClass_voidMethod() {
try(var arena = SwiftArena.ofConfined()) {
MySwiftClass o = new MySwiftClass(12, 42, arena);
MySwiftClass o = MySwiftClass.init(12, 42, arena);
o.voidMethod();
} catch (Throwable throwable) {
checkPaths(throwable);
Expand All @@ -53,7 +53,7 @@ void test_MySwiftClass_voidMethod() {
@Test
void test_MySwiftClass_makeIntMethod() {
try(var arena = SwiftArena.ofConfined()) {
MySwiftClass o = new MySwiftClass(12, 42, arena);
MySwiftClass o = MySwiftClass.init(12, 42, arena);
var got = o.makeIntMethod();
assertEquals(12, got);
}
Expand All @@ -63,7 +63,7 @@ void test_MySwiftClass_makeIntMethod() {
@Disabled // TODO: Need var mangled names in interfaces
void test_MySwiftClass_property_len() {
try(var arena = SwiftArena.ofConfined()) {
MySwiftClass o = new MySwiftClass(12, 42, arena);
MySwiftClass o = MySwiftClass.init(12, 42, arena);
var got = o.getLen();
assertEquals(12, got);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class MySwiftClassTest {
@Test
void call_retain_retainCount_release() {
var arena = SwiftArena.ofConfined();
var obj = new MySwiftClass(1, 2, arena);
var obj = MySwiftClass.init(1, 2, arena);

assertEquals(1, SwiftKit.retainCount(obj));
// TODO: test directly on SwiftHeapObject inheriting obj
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ static boolean isAmd64() {
@DisabledIf("isAmd64")
public void arena_releaseClassOnClose_class_ok() {
try (var arena = SwiftArena.ofConfined()) {
var obj = new MySwiftClass(1, 2, arena);
var obj = MySwiftClass.init(1, 2, arena);

retain(obj);
assertEquals(2, retainCount(obj));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static class BenchmarkState {
@Setup(Level.Trial)
public void beforeAll() {
arena = SwiftArena.ofConfined();
obj = new MySwiftClass(1, 2, arena);
obj = MySwiftClass.init(1, 2, arena);
}

@TearDown(Level.Trial)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public class StringPassingBenchmark {
@Setup(Level.Trial)
public void beforeAll() {
arena = SwiftArena.ofConfined();
obj = new MySwiftClass(1, 2, arena);
obj = MySwiftClass.init(1, 2, arena);
string = makeString(stringLen);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ static void examples() {

// Example of using an arena; MyClass.deinit is run at end of scope
try (var arena = SwiftArena.ofConfined()) {
MySwiftClass obj = new MySwiftClass(2222, 7777, arena);
MySwiftClass obj = MySwiftClass.init(2222, 7777, arena);

// just checking retains/releases work
SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
Expand All @@ -65,7 +65,7 @@ static void examples() {
MySwiftClass otherObj = MySwiftClass.factory(12, 42, arena);
otherObj.voidMethod();

MySwiftStruct swiftValue = new MySwiftStruct(2222, 1111, arena);
MySwiftStruct swiftValue = MySwiftStruct.init(2222, 1111, arena);
SwiftKit.trace("swiftValue.capacity = " + swiftValue.getCapacity());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void checkPaths(Throwable throwable) {
@Test
void test_MySwiftClass_voidMethod() {
try(var arena = SwiftArena.ofConfined()) {
MySwiftClass o = new MySwiftClass(12, 42, arena);
MySwiftClass o = MySwiftClass.init(12, 42, arena);
o.voidMethod();
} catch (Throwable throwable) {
checkPaths(throwable);
Expand All @@ -52,7 +52,7 @@ void test_MySwiftClass_voidMethod() {
@Test
void test_MySwiftClass_makeIntMethod() {
try(var arena = SwiftArena.ofConfined()) {
MySwiftClass o = new MySwiftClass(12, 42, arena);
MySwiftClass o = MySwiftClass.init(12, 42, arena);
var got = o.makeIntMethod();
assertEquals(12, got);
}
Expand All @@ -62,7 +62,7 @@ void test_MySwiftClass_makeIntMethod() {
@Disabled // TODO: Need var mangled names in interfaces
void test_MySwiftClass_property_len() {
try(var arena = SwiftArena.ofConfined()) {
MySwiftClass o = new MySwiftClass(12, 42, arena);
MySwiftClass o = MySwiftClass.init(12, 42, arena);
var got = o.getLen();
assertEquals(12, got);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class MySwiftClassTest {
@Test
void call_retain_retainCount_release() {
var arena = SwiftArena.ofConfined();
var obj = new MySwiftClass(1, 2, arena);
var obj = MySwiftClass.init(1, 2, arena);

assertEquals(1, SwiftKit.retainCount(obj));
// TODO: test directly on SwiftHeapObject inheriting obj
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ void create_struct() {
try (var arena = SwiftArena.ofConfined()) {
long cap = 12;
long len = 34;
var struct = new MySwiftStruct(cap, len, arena);
var struct = MySwiftStruct.init(cap, len, arena);

assertEquals(cap, struct.getCapacity());
assertEquals(len, struct.getLength());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ static boolean isAmd64() {
@DisabledIf("isAmd64")
public void arena_releaseClassOnClose_class_ok() {
try (var arena = SwiftArena.ofConfined()) {
var obj = new MySwiftClass(1, 2, arena);
var obj = MySwiftClass.init(1, 2, arena);

retain(obj);
assertEquals(2, retainCount(obj));
Expand All @@ -57,7 +57,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_class() {
MySwiftClass unsafelyEscapedOutsideArenaScope = null;

try (var arena = SwiftArena.ofConfined()) {
var obj = new MySwiftClass(1, 2, arena);
var obj = MySwiftClass.init(1, 2, arena);
unsafelyEscapedOutsideArenaScope = obj;
}

Expand All @@ -76,7 +76,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_struct() {
MySwiftStruct unsafelyEscapedOutsideArenaScope = null;

try (var arena = SwiftArena.ofConfined()) {
var s = new MySwiftStruct(1, 2, arena);
var s = MySwiftStruct.init(1, 2, arena);
unsafelyEscapedOutsideArenaScope = s;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,6 @@
import JavaTypes

extension Swift2JavaTranslator {
public func printInitializerDowncallConstructors(
_ printer: inout CodePrinter,
_ decl: ImportedFunc
) {
printer.printSeparator(decl.displayName)

printJavaBindingDescriptorClass(&printer, decl)

// Render the "make the downcall" functions.
printInitializerDowncallConstructor(&printer, decl)
}

public func printFunctionDowncallMethods(
_ printer: inout CodePrinter,
_ decl: ImportedFunc
Expand Down Expand Up @@ -86,43 +74,6 @@ extension Swift2JavaTranslator {
printer.print(");")
}

public func printInitializerDowncallConstructor(
_ printer: inout CodePrinter,
_ decl: ImportedFunc
) {
guard let className = decl.parentType?.asNominalTypeDeclaration?.name else {
return
}
let modifiers = "public"

var paramDecls = decl.translatedSignature.parameters
.flatMap(\.javaParameters)
.map { "\($0.type) \($0.name)" }

assert(decl.translatedSignature.requiresSwiftArena, "constructor always require the SwiftArena")
paramDecls.append("SwiftArena swiftArena$")

printer.printBraceBlock(
"""
/**
* Create an instance of {@code \(className)}.
*
* {@snippet lang=swift :
* \(decl.signatureString)
* }
*/
\(modifiers) \(className)(\(paramDecls.joined(separator: ", ")))
"""
) { printer in
// Call super constructor `SwiftValue(Supplier <MemorySegment>, SwiftArena)`.
printer.print("super(() -> {")
printer.indent()
printDowncall(&printer, decl, isConstructor: true)
printer.outdent()
printer.print("}, swiftArena$);")
}
}

/// Print the calling body that forwards all the parameters to the `methodName`,
/// with adding `SwiftArena.ofAuto()` at the end.
public func printFuncDowncallMethod(
Expand All @@ -131,13 +82,12 @@ extension Swift2JavaTranslator {
let methodName: String = switch decl.kind {
case .getter: "get\(decl.name.toCamelCase)"
case .setter: "set\(decl.name.toCamelCase)"
case .function: decl.name
case .initializer: fatalError("initializers must use printInitializerDowncallConstructor()")
case .function, .initializer: decl.name
}

var modifiers = "public"
switch decl.swiftSignature.selfParameter {
case .staticMethod(_), nil:
case .staticMethod, .initializer, nil:
modifiers.append(" static")
default:
break
Expand Down Expand Up @@ -178,8 +128,7 @@ extension Swift2JavaTranslator {
/// This assumes that all the parameters are passed-in with appropriate names.
package func printDowncall(
_ printer: inout CodePrinter,
_ decl: ImportedFunc,
isConstructor: Bool = false
_ decl: ImportedFunc
) {
//=== Part 1: MethodHandle
let descriptorClassIdentifier = thunkNameRegistry.functionThunkName(decl: decl)
Expand Down Expand Up @@ -252,11 +201,7 @@ extension Swift2JavaTranslator {
let downCall = "mh$.invokeExact(\(downCallArguments.joined(separator: ", ")))"

//=== Part 4: Convert the return value.
if isConstructor {
// For constructors, the caller expects the "self" memory segment.
printer.print("\(downCall);")
printer.print("return _result;")
} else if decl.translatedSignature.result.javaResultType == .void {
if decl.translatedSignature.result.javaResultType == .void {
printer.print("\(downCall);")
} else {
let placeholder: String
Expand Down
2 changes: 1 addition & 1 deletion Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ extension Swift2JavaTranslator {

// Initializers
for initDecl in decl.initializers {
printInitializerDowncallConstructors(&printer, initDecl)
printFunctionDowncallMethods(&printer, initDecl)
}

// Properties
Expand Down
13 changes: 0 additions & 13 deletions SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,6 @@ protected SwiftInstance(MemorySegment segment, SwiftArena arena) {
arena.register(this);
}

/**
* Convenience constructor subclasses can call like:
* {@snippet :
* super(() -> { ...; return segment; }, swiftArena$)
* }
*
* @param segmentSupplier Should return the memory segment of the value
* @param arena the arena where the supplied segment belongs to. When the arena goes out of scope, this value is destroyed.
*/
protected SwiftInstance(Supplier<MemorySegment> segmentSupplier, SwiftArena arena) {
this(segmentSupplier.get(), arena);
}

/**
* Ensures that this instance has not been destroyed.
* <p/>
Expand Down
56 changes: 25 additions & 31 deletions Tests/JExtractSwiftTests/MethodImportTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -334,34 +334,31 @@ final class MethodImportTests {
}!

let output = CodePrinter.toString { printer in
st.printInitializerDowncallConstructor(&printer, initDecl)
st.printFuncDowncallMethod(&printer, initDecl)
}

assertOutput(
output,
expected:
"""
/**
* Create an instance of {@code MySwiftClass}.
*
* Downcall to Swift:
* {@snippet lang=swift :
* public init(len: Swift.Int, cap: Swift.Int)
* }
*/
public MySwiftClass(long len, long cap, SwiftArena swiftArena$) {
super(() -> {
var mh$ = swiftjava___FakeModule_MySwiftClass_init_len_cap.HANDLE;
try {
MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT);
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(len, cap, _result);
}
mh$.invokeExact(len, cap, _result);
return _result;
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
public static MySwiftClass init(long len, long cap, SwiftArena swiftArena$) {
var mh$ = swiftjava___FakeModule_MySwiftClass_init_len_cap.HANDLE;
try {
MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT);
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(len, cap, _result);
}
}, swiftArena$);
mh$.invokeExact(len, cap, _result);
return new MySwiftClass(_result, swiftArena$);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
}
"""
)
Expand All @@ -382,34 +379,31 @@ final class MethodImportTests {
}!

let output = CodePrinter.toString { printer in
st.printInitializerDowncallConstructor(&printer, initDecl)
st.printFuncDowncallMethod(&printer, initDecl)
}

assertOutput(
output,
expected:
"""
/**
* Create an instance of {@code MySwiftStruct}.
*
* Downcall to Swift:
* {@snippet lang=swift :
* public init(len: Swift.Int, cap: Swift.Int)
* }
*/
public MySwiftStruct(long len, long cap, SwiftArena swiftArena$) {
super(() -> {
var mh$ = swiftjava___FakeModule_MySwiftStruct_init_len_cap.HANDLE;
try {
MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT);
if (SwiftKit.TRACE_DOWNCALLS) {
public static MySwiftStruct init(long len, long cap, SwiftArena swiftArena$) {
var mh$ = swiftjava___FakeModule_MySwiftStruct_init_len_cap.HANDLE;
try {
MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT);
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(len, cap, _result);
}
mh$.invokeExact(len, cap, _result);
return _result;
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
}, swiftArena$);
mh$.invokeExact(len, cap, _result);
return new MySwiftStruct(_result, swiftArena$);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
}
"""
)
Expand Down