Skip to content

Commit 71670be

Browse files
committed
Expose Java-friendly instance methods and constructors for Swift types
When projecting a Swift type into a Java class, map Swift methods into instance methods (rather than static methods) and Swift initializers into Java constructors (rather than static 'init' methods).
1 parent 885b45a commit 71670be

File tree

6 files changed

+93
-22
lines changed

6 files changed

+93
-22
lines changed

JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,12 @@ static void tests() {
6060

6161
JavaKitExample.globalTakeInt(1337);
6262

63-
MySwiftClass obj = MySwiftClass.init(2222, 7777);
63+
MySwiftClass obj = new MySwiftClass(2222, 7777);
6464

6565
SwiftKit.retain(obj.$memorySegment());
6666
System.out.println("[java] obj ref count = " + SwiftKit.retainCount(obj.$memorySegment()));
67+
68+
obj.voidMethod();
69+
obj.takeIntMethod(42);
6770
}
6871
}

Sources/JExtractSwift/ImportedDecls.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ public struct ImportedNominalType: ImportedDecl {
4848
javaType: javaType
4949
)
5050
}
51+
52+
/// The Java class name without the package.
53+
public var javaClassName: String {
54+
switch javaType {
55+
case .class(package: _, name: let name): name
56+
default: javaType.description
57+
}
58+
}
5159
}
5260

5361
public enum NominalTypeKind {
@@ -116,7 +124,7 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
116124
/// this will contain that declaration's imported name.
117125
///
118126
/// This is necessary when rendering accessor Java code we need the type that "self" is expecting to have.
119-
var parentName: TranslatedType?
127+
public var parentName: TranslatedType?
120128
public var hasParent: Bool { parentName != nil }
121129

122130
/// This is a full name such as init(cap:name:).
@@ -152,7 +160,7 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
152160
//
153161
// allocating initializer takes a Self.Type instead, but it's also a pointer
154162
switch selfVariant {
155-
case nil:
163+
case nil, .wrapper:
156164
break
157165

158166
case .pointer:
@@ -174,10 +182,6 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
174182
type: parentForSelf
175183
)
176184
)
177-
178-
case .wrapper:
179-
let selfParam: FunctionParameterSyntax = "self$: \(raw: parentName.swiftTypeName)"
180-
params.append(ImportedParam(param: selfParam, type: parentName))
181185
}
182186

183187
// TODO: add any metadata for generics and other things we may need to add here

Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ extension Swift2JavaTranslator {
2828
var printer = CodePrinter()
2929

3030
for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
31-
let filename = "\(ty.javaType).java"
31+
let filename = "\(ty.javaClassName).java"
3232
log.info("Printing contents: \(filename)")
3333
printImportedClass(&printer, ty)
3434

@@ -165,7 +165,7 @@ extension Swift2JavaTranslator {
165165
}
166166

167167
public func printClass(_ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void) {
168-
printer.printTypeDecl("public final class \(decl.javaType)") { printer in
168+
printer.printTypeDecl("public final class \(decl.javaClassName)") { printer in
169169
// ==== Storage of the class
170170
// FIXME: implement the self storage for the memory address and accessors
171171
printClassSelfProperty(&printer, decl)
@@ -274,7 +274,7 @@ extension Swift2JavaTranslator {
274274
printer.print(
275275
"""
276276
/** Instances are created using static {@code init} methods rather than through the constructor directly. */
277-
private \(decl.javaType)(MemorySegment selfMemorySegment) {
277+
private \(decl.javaClassName)(MemorySegment selfMemorySegment) {
278278
this.selfMemorySegment = selfMemorySegment;
279279
}
280280
"""
@@ -412,24 +412,32 @@ extension Swift2JavaTranslator {
412412
printMethodDowncallHandleForAddrDesc(&printer)
413413
}
414414

415+
printClassInitializerConstructor(&printer, decl, parentName: parentName)
416+
}
417+
418+
public func printClassInitializerConstructor(
419+
_ printer: inout CodePrinter,
420+
_ decl: ImportedFunc,
421+
parentName: TranslatedType
422+
) {
423+
let descClassIdentifier = renderDescClassName(decl)
415424
printer.print(
416425
"""
417426
/**
418-
* Create an instance of the {@code \(parentName.javaType)} Swift class.
427+
* Create an instance of {@code \(parentName.unqualifiedJavaTypeName)}.
419428
*
420-
* {@snippet lang=Swift:
429+
* {@snippet lang=swift :
421430
* \(decl.swiftDeclRaw ?? "")
422431
* }
423432
*/
424-
public static \(parentName.javaType) init(\(renderJavaParamDecls(decl, selfVariant: .none))) {
433+
public \(parentName.unqualifiedJavaTypeName)(\(renderJavaParamDecls(decl, selfVariant: .wrapper))) {
425434
var mh$ = \(descClassIdentifier).HANDLE;
426435
try {
427436
if (TRACE_DOWNCALLS) {
428437
traceDowncall(\(renderForwardParams(decl, selfVariant: nil)));
429438
}
430439
431-
var self = (MemorySegment) mh$.invokeExact(\(renderForwardParams(decl, selfVariant: nil)), TYPE_METADATA);
432-
return new \(parentName.javaType)(self);
440+
this.selfMemorySegment = (MemorySegment) mh$.invokeExact(\(renderForwardParams(decl, selfVariant: nil)), TYPE_METADATA);
433441
} catch (Throwable ex$) {
434442
throw new AssertionError("should not reach here", ex$);
435443
}
@@ -542,8 +550,8 @@ extension Swift2JavaTranslator {
542550
* \(/*TODO: make a printSnippet func*/decl.swiftDeclRaw ?? "")
543551
* }
544552
*/
545-
public static \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, selfVariant: .wrapper))) {
546-
\(maybeReturnCast) \(decl.baseIdentifier)(\(renderForwardParams(decl, selfVariant: .memorySegment)));
553+
public \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, selfVariant: .wrapper))) {
554+
\(maybeReturnCast) \(decl.baseIdentifier)(\(renderForwardParams(decl, selfVariant: .wrapper)));
547555
}
548556
"""
549557
)
@@ -630,6 +638,11 @@ extension Swift2JavaTranslator {
630638
ps.append(param)
631639
}
632640

641+
// Add the forwarding "self"
642+
if selfVariant == .wrapper && !decl.isInit {
643+
ps.append("$memorySegment()")
644+
}
645+
633646
return ps.joined(separator: ", ")
634647
}
635648

Sources/JExtractSwift/Swift2JavaVisitor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
163163
}
164164

165165
let initIdentifier =
166-
"init(\(params.compactMap { $0.effectiveName ?? "_" }.joined(separator: ":")))"
166+
"init(\(String(params.flatMap { "\($0.effectiveName ?? "_"):" })))"
167167

168168
var funcDecl = ImportedFunc(
169169
parentName: currentType.translatedType,

Sources/JExtractSwift/TranslatedType.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,14 @@ public struct TranslatedType {
223223
var swiftTypeName: String {
224224
originalSwiftType.trimmedDescription
225225
}
226+
227+
/// Produce the "unqualified" Java type name.
228+
var unqualifiedJavaTypeName: String {
229+
switch javaType {
230+
case .class(package: _, name: let name): name
231+
default: javaType.description
232+
}
233+
}
226234
}
227235

228236
/// Describes the C-compatible layout as it should be referenced from Java.

Tests/JExtractSwiftTests/FuncImportTests.swift

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,8 @@ final class MethodImportTests {
331331
* public func helloMemberFunction()
332332
* }
333333
*/
334-
public static void helloMemberFunction(com.example.swift.MySwiftClass self$) {
335-
helloMemberFunction(self$);
334+
public void helloMemberFunction() {
335+
helloMemberFunction($memorySegment());
336336
}
337337
"""
338338
)
@@ -364,11 +364,54 @@ final class MethodImportTests {
364364
* public func makeInt() -> Int
365365
* }
366366
*/
367-
public static long makeInt(com.example.swift.MySwiftClass self$) {
368-
return (long) makeInt(self$);
367+
public long makeInt() {
368+
return (long) makeInt($memorySegment());
369369
}
370370
"""
371371
)
372372
}
373373

374+
@Test func class_constructor() async throws {
375+
let st = Swift2JavaTranslator(
376+
javaPackage: "com.example.swift",
377+
swiftModuleName: "__FakeModule"
378+
)
379+
st.log.logLevel = .trace
380+
381+
try await st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile)
382+
383+
let initDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.initializers.first {
384+
$0.identifier == "init(len:cap:)"
385+
}!
386+
387+
let output = CodePrinter.toString { printer in
388+
st.printClassInitializerConstructor(&printer, initDecl, parentName: initDecl.parentName!)
389+
}
390+
391+
assertOutput(
392+
output,
393+
expected:
394+
"""
395+
/**
396+
* Create an instance of {@code MySwiftClass}.
397+
*
398+
* {@snippet lang=swift :
399+
* public init(len: Swift.Int, cap: Swift.Int)
400+
* }
401+
*/
402+
public MySwiftClass(long len, long cap) {
403+
var mh$ = init_len_cap.HANDLE;
404+
try {
405+
if (TRACE_DOWNCALLS) {
406+
traceDowncall(len, cap);
407+
}
408+
409+
this.selfMemorySegment = (MemorySegment) mh$.invokeExact(len, cap, TYPE_METADATA);
410+
} catch (Throwable ex$) {
411+
throw new AssertionError(\"should not reach here\", ex$);
412+
}
413+
}
414+
"""
415+
)
416+
}
374417
}

0 commit comments

Comments
 (0)