Skip to content

Commit e1f0a8c

Browse files
committed
generate java bindings for initializers
1 parent d28959a commit e1f0a8c

File tree

6 files changed

+250
-59
lines changed

6 files changed

+250
-59
lines changed

Sources/JExtractSwiftLib/ImportedDecls.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ package class ImportedNominalType: ImportedDecl {
4040
var swiftType: SwiftType {
4141
return .nominal(.init(nominalTypeDecl: swiftNominal))
4242
}
43+
44+
var qualifiedName: String {
45+
self.swiftNominal.qualifiedName
46+
}
4347
}
4448

4549
public final class ImportedFunc: ImportedDecl, CustomStringConvertible {

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,20 @@ extension JNISwift2JavaGenerator {
7474
printPackage(&printer)
7575

7676
printNominal(&printer, decl) { printer in
77+
printer.print(
78+
"""
79+
private long selfPointer;
80+
81+
private MyClass(long selfPointer) {
82+
this.selfPointer = selfPointer;
83+
}
84+
"""
85+
)
86+
87+
for initializer in decl.initializers {
88+
printInitializerBindings(&printer, initializer, type: decl)
89+
}
90+
7791
for method in decl.methods {
7892
printFunctionBinding(&printer, method)
7993
}
@@ -114,12 +128,31 @@ extension JNISwift2JavaGenerator {
114128
}
115129

116130
private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
117-
let returnType = decl.functionSignature.result.type.javaType
118-
let params = decl.functionSignature.parameters.enumerated().map { idx, param in
119-
"\(param.type.javaType) \(param.parameterName ?? "arg\(idx))")"
131+
printDeclDocumentation(&printer, decl)
132+
printer.print(
133+
"public static native \(renderFunctionSignature(decl));"
134+
)
135+
}
136+
137+
private func printInitializerBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc, type: ImportedNominalType) {
138+
let translatedDecl = translatedDecl(for: decl)
139+
140+
printDeclDocumentation(&printer, decl)
141+
printer.printBraceBlock("public static \(renderFunctionSignature(decl))") { printer in
142+
let initArguments = translatedDecl.translatedFunctionSignature.parameters.map(\.asArgument)
143+
printer.print(
144+
"""
145+
long selfPointer = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", ")));
146+
return new \(type.qualifiedName)(selfPointer);
147+
"""
148+
)
120149
}
121-
let throwsClause = decl.isThrowing ? " throws Exception" : ""
122150

151+
let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter)
152+
printer.print("private static native long allocatingInit(\(parameters.joined(separator: ", ")));")
153+
}
154+
155+
private func printDeclDocumentation(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
123156
printer.print(
124157
"""
125158
/**
@@ -130,8 +163,18 @@ extension JNISwift2JavaGenerator {
130163
*/
131164
"""
132165
)
133-
printer.print(
134-
"public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")))\(throwsClause);"
135-
)
166+
}
167+
168+
/// Renders a function signature such
169+
///
170+
/// `func method(x: Int, y: Int) -> Int` becomes
171+
/// `long method(long x, long y)`
172+
private func renderFunctionSignature(_ decl: ImportedFunc) -> String {
173+
let translatedDecl = translatedDecl(for: decl)
174+
let resultType = translatedDecl.translatedFunctionSignature.resultType
175+
let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter)
176+
let throwsClause = decl.isThrowing ? " throws Exception" : ""
177+
178+
return "\(resultType) \(decl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)"
136179
}
137180
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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 JavaTypes
16+
17+
extension JNISwift2JavaGenerator {
18+
func translatedDecl(
19+
for decl: ImportedFunc
20+
) -> TranslatedFunctionDecl {
21+
if let cached = translatedDecls[decl] {
22+
return cached
23+
}
24+
25+
let translation = JavaTranslation()
26+
let translated = translation.translate(decl)
27+
28+
translatedDecls[decl] = translated
29+
return translated
30+
}
31+
32+
struct JavaTranslation {
33+
func translate(_ decl: ImportedFunc) -> TranslatedFunctionDecl {
34+
let translatedFunctionSignature = translate(functionSignature: decl.functionSignature)
35+
36+
return TranslatedFunctionDecl(
37+
name: decl.name,
38+
translatedFunctionSignature: translatedFunctionSignature
39+
)
40+
}
41+
42+
func translate(functionSignature: SwiftFunctionSignature) -> TranslatedFunctionSignature {
43+
let parameters = functionSignature.parameters.enumerated().map { idx, param in
44+
let parameterName = param.parameterName ?? "arg\(idx))"
45+
return translate(swiftParam: param, parameterName: parameterName)
46+
}
47+
48+
return TranslatedFunctionSignature(
49+
parameters: parameters,
50+
resultType: translate(swiftType: functionSignature.result.type)
51+
)
52+
}
53+
54+
func translate(swiftParam: SwiftParameter, parameterName: String) -> JavaParameter {
55+
return JavaParameter(
56+
name: parameterName,
57+
type: translate(swiftType: swiftParam.type)
58+
)
59+
}
60+
61+
func translate(swiftType: SwiftType) -> JavaType {
62+
switch swiftType {
63+
case .nominal(let nominalType):
64+
if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType {
65+
guard let javaType = translate(standardLibraryType: knownType) else {
66+
fatalError("unsupported known type: \(knownType)")
67+
}
68+
return javaType
69+
}
70+
71+
return .class(package: nil, name: nominalType.nominalTypeDecl.name)
72+
73+
case .tuple([]):
74+
return .void
75+
76+
case .metatype, .optional, .tuple, .function:
77+
fatalError("unsupported type: \(self)")
78+
}
79+
}
80+
81+
func translate(standardLibraryType: SwiftStandardLibraryTypeKind) -> JavaType? {
82+
switch standardLibraryType {
83+
case .bool: .boolean
84+
case .int8: .byte
85+
case .uint16: .char
86+
case .int16: .short
87+
case .int32: .int
88+
case .int64: .long
89+
case .float: .float
90+
case .double: .double
91+
case .void: .void
92+
case .string: .javaLangString
93+
case .int, .uint, .uint8, .uint32, .uint64,
94+
.unsafeRawPointer, .unsafeMutableRawPointer,
95+
.unsafePointer, .unsafeMutablePointer,
96+
.unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
97+
.unsafeBufferPointer, .unsafeMutableBufferPointer:
98+
nil
99+
}
100+
}
101+
}
102+
103+
struct TranslatedFunctionDecl {
104+
/// Java function name
105+
let name: String
106+
107+
/// Function signature
108+
let translatedFunctionSignature: TranslatedFunctionSignature
109+
}
110+
111+
struct JavaParameter {
112+
let name: String
113+
let type: JavaType
114+
115+
var asParameter: String {
116+
"\(type) \(name)"
117+
}
118+
119+
var asArgument: String {
120+
name
121+
}
122+
}
123+
124+
struct TranslatedFunctionSignature {
125+
let parameters: [JavaParameter]
126+
let resultType: JavaType
127+
}
128+
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,16 @@ extension JNISwift2JavaGenerator {
9696
_ decl: ImportedFunc,
9797
sorroundingType: ImportedNominalType? = nil
9898
) {
99+
let translatedDecl = translatedDecl(for: decl)
99100
let parentName = sorroundingType?.swiftNominal.qualifiedName ?? swiftModuleName
100101

101102
let cName =
102103
"Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") + "_\(parentName)_"
103104
+ decl.name
104105
let thunkName = thunkNameRegistry.functionThunkName(decl: decl)
105-
let translatedParameters = decl.functionSignature.parameters.enumerated().map { idx, param in
106-
(param.parameterName ?? "arg\(idx)", param.type.javaType)
106+
// TODO: Add a similair construct as `LoweredFunctionSignature` to `TranslatedFunctionSignature`
107+
let translatedParameters = translatedDecl.translatedFunctionSignature.parameters.map { param in
108+
(param.name, param.type)
107109
}
108110

109111
let thunkParameters =
@@ -113,7 +115,7 @@ extension JNISwift2JavaGenerator {
113115
] + translatedParameters.map { "\($0.0): \($0.1.jniTypeName)" }
114116
let swiftReturnType = decl.functionSignature.result.type
115117
let thunkReturnType =
116-
!swiftReturnType.isVoid ? " -> \(swiftReturnType.javaType.jniTypeName)" : ""
118+
!swiftReturnType.isVoid ? " -> \(translatedDecl.translatedFunctionSignature.resultType.jniTypeName)" : ""
117119

118120
printer.printBraceBlock(
119121
"""

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator {
2828

2929
var thunkNameRegistry = ThunkNameRegistry()
3030

31+
/// Cached Java translation result. 'nil' indicates failed translation.
32+
var translatedDecls: [ImportedFunc: TranslatedFunctionDecl] = [:]
33+
3134
/// Because we need to write empty files for SwiftPM, keep track which files we didn't write yet,
3235
/// and write an empty file for those.
3336
var expectedOutputSwiftFiles: Set<String>
@@ -71,49 +74,3 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator {
7174
}
7275
}
7376
}
74-
75-
extension SwiftType {
76-
var javaType: JavaType {
77-
switch self {
78-
case .nominal(let nominalType):
79-
if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType {
80-
guard let javaType = knownType.javaType else {
81-
fatalError("unsupported known type: \(knownType)")
82-
}
83-
return javaType
84-
}
85-
86-
fatalError("unsupported nominal type: \(nominalType)")
87-
88-
case .tuple([]):
89-
return .void
90-
91-
case .metatype, .optional, .tuple, .function:
92-
fatalError("unsupported type: \(self)")
93-
}
94-
}
95-
}
96-
97-
extension SwiftStandardLibraryTypeKind {
98-
var javaType: JavaType? {
99-
switch self {
100-
case .bool: .boolean
101-
case .int: .long // TODO: Handle 32-bit or 64-bit
102-
case .int8: .byte
103-
case .uint16: .char
104-
case .int16: .short
105-
case .int32: .int
106-
case .int64: .long
107-
case .float: .float
108-
case .double: .double
109-
case .void: .void
110-
case .string: .javaLangString
111-
case .uint, .uint8, .uint32, .uint64,
112-
.unsafeRawPointer, .unsafeMutableRawPointer,
113-
.unsafePointer, .unsafeMutablePointer,
114-
.unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
115-
.unsafeBufferPointer, .unsafeMutableBufferPointer:
116-
nil
117-
}
118-
}
119-
}

Tests/JExtractSwiftTests/JNI/JNIClassTests.swift

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,19 @@ import Testing
1919
struct JNIClassTests {
2020
let source = """
2121
public class MyClass {
22-
public static func method() {
22+
let x: Int64
23+
let y: Int64
2324
25+
public static func method() {}
26+
27+
public init(x: Int64, y: Int64) {
28+
self.x = y
29+
self.y = y
30+
}
31+
32+
public init() {
33+
self.x = 0
34+
self.y = 0
2435
}
2536
}
2637
"""
@@ -34,8 +45,13 @@ struct JNIClassTests {
3445
3546
package com.example.swift;
3647
37-
public final class MyClass {
38-
"""
48+
public final class MyClass {
49+
private long selfPointer;
50+
51+
private MyClass(long selfPointer) {
52+
this.selfPointer = selfPointer;
53+
}
54+
""",
3955
])
4056
}
4157

@@ -76,4 +92,45 @@ struct JNIClassTests {
7692
]
7793
)
7894
}
95+
96+
@Test
97+
func initializer_javaBindings() throws {
98+
try assertOutput(
99+
input: source,
100+
.jni,
101+
.java,
102+
expectedChunks: [
103+
"""
104+
/**
105+
* Downcall to Swift:
106+
* {@snippet lang=swift :
107+
* public init(x: Int64, y: Int64)
108+
* }
109+
*/
110+
public static MyClass init(long x, long y) {
111+
long selfPointer = MyClass.allocatingInit(x, y);
112+
return new MyClass(selfPointer);
113+
}
114+
""",
115+
"""
116+
/**
117+
* Downcall to Swift:
118+
* {@snippet lang=swift :
119+
* public init()
120+
* }
121+
*/
122+
public static MyClass init() {
123+
long selfPointer = MyClass.allocatingInit();
124+
return new MyClass(selfPointer);
125+
}
126+
""",
127+
"""
128+
private static native long allocatingInit(long x, long y);
129+
""",
130+
"""
131+
private static native long allocatingInit();
132+
"""
133+
]
134+
)
135+
}
79136
}

0 commit comments

Comments
 (0)