Skip to content

Commit 5efd8c5

Browse files
committed
add simple support for getting associated values
1 parent e74299e commit 5efd8c5

File tree

10 files changed

+373
-24
lines changed

10 files changed

+373
-24
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 20245Apple 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+
package com.example.swift;
16+
17+
import org.openjdk.jmh.annotations.*;
18+
import org.openjdk.jmh.infra.Blackhole;
19+
import org.swift.swiftkit.core.ClosableSwiftArena;
20+
import org.swift.swiftkit.core.ConfinedSwiftMemorySession;
21+
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
import java.util.Optional;
25+
import java.util.concurrent.TimeUnit;
26+
27+
@BenchmarkMode(Mode.AverageTime)
28+
@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
29+
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
30+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
31+
@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" })
32+
public class EnumBenchmark {
33+
34+
@State(Scope.Benchmark)
35+
public static class BenchmarkState {
36+
ClosableSwiftArena arena;
37+
Vehicle vehicle;
38+
39+
@Setup(Level.Trial)
40+
public void beforeAll() {
41+
arena = new ConfinedSwiftMemorySession();
42+
vehicle = Vehicle.motorbike("Yamaha", 900, arena);
43+
}
44+
45+
@TearDown(Level.Trial)
46+
public void afterAll() {
47+
arena.close();
48+
}
49+
}
50+
51+
@Benchmark
52+
public Vehicle.Motorbike java_copy(BenchmarkState state, Blackhole bh) {
53+
Vehicle.Motorbike motorbike = state.vehicle.getAsMotorbike().orElseThrow();
54+
bh.consume(motorbike.arg0());
55+
bh.consume(motorbike.horsePower());
56+
57+
return motorbike;
58+
}
59+
}

Samples/JExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,46 @@ void upgrade() {
8585
assertEquals("motorbike", vehicle.getName());
8686
}
8787
}
88+
89+
@Test
90+
void getAsBicycle() {
91+
try (var arena = new ConfinedSwiftMemorySession()) {
92+
Vehicle vehicle = Vehicle.bicycle(arena);
93+
Vehicle.Bicycle bicycle = vehicle.getAsBicycle().orElseThrow();
94+
assertNotNull(bicycle);
95+
}
96+
}
97+
98+
@Test
99+
void getAsCar() {
100+
try (var arena = new ConfinedSwiftMemorySession()) {
101+
Vehicle vehicle = Vehicle.car("BMW", arena);
102+
Vehicle.Car car = vehicle.getAsCar().orElseThrow();
103+
assertEquals("BMW", car.arg0());
104+
}
105+
}
106+
107+
@Test
108+
void getAsMotorbike() {
109+
try (var arena = new ConfinedSwiftMemorySession()) {
110+
Vehicle vehicle = Vehicle.motorbike("Yamaha", 750, arena);
111+
Vehicle.Motorbike motorbike = vehicle.getAsMotorbike().orElseThrow();
112+
assertEquals("Yamaha", motorbike.arg0());
113+
assertEquals(750, motorbike.horsePower());
114+
}
115+
}
116+
117+
@Test
118+
void associatedValuesAreCopied() {
119+
try (var arena = new ConfinedSwiftMemorySession()) {
120+
Vehicle vehicle = Vehicle.car("BMW", arena);
121+
Vehicle.Car car = vehicle.getAsCar().orElseThrow();
122+
assertEquals("BMW", car.arg0());
123+
vehicle.upgrade();
124+
Vehicle.Motorbike motorbike = vehicle.getAsMotorbike().orElseThrow();
125+
assertNotNull(motorbike);
126+
// Motorbike should still remain
127+
assertEquals("BMW", car.arg0());
128+
}
129+
}
88130
}

Sources/JExtractSwiftLib/ImportedDecls.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,24 @@ public final class ImportedEnumCase: ImportedDecl, CustomStringConvertible {
5555
/// The enum parameters
5656
var parameters: [SwiftEnumCaseParameter]
5757

58+
var swiftDecl: any DeclSyntaxProtocol
59+
60+
var enumType: SwiftNominalType
61+
5862
/// A function that represents the Swift static "initializer" for cases
5963
var caseFunction: ImportedFunc
6064

61-
init(name: String, parameters: [SwiftEnumCaseParameter], caseFunction: ImportedFunc) {
65+
init(
66+
name: String,
67+
parameters: [SwiftEnumCaseParameter],
68+
swiftDecl: any DeclSyntaxProtocol,
69+
enumType: SwiftNominalType,
70+
caseFunction: ImportedFunc
71+
) {
6272
self.name = name
6373
self.parameters = parameters
74+
self.swiftDecl = swiftDecl
75+
self.enumType = enumType
6476
self.caseFunction = caseFunction
6577
}
6678

@@ -69,12 +81,23 @@ public final class ImportedEnumCase: ImportedDecl, CustomStringConvertible {
6981
ImportedEnumCase {
7082
name: \(name),
7183
parameters: \(parameters),
84+
swiftDecl: \(swiftDecl),
85+
enumType: \(enumType),
7286
caseFunction: \(caseFunction)
7387
}
7488
"""
7589
}
7690
}
7791

92+
extension ImportedEnumCase: Hashable {
93+
public func hash(into hasher: inout Hasher) {
94+
hasher.combine(ObjectIdentifier(self))
95+
}
96+
public static func == (lhs: ImportedEnumCase, rhs: ImportedEnumCase) -> Bool {
97+
return lhs === rhs
98+
}
99+
}
100+
78101
public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
79102
/// Swift module name (e.g. the target name where a type or function was declared)
80103
public var module: String

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ extension JNISwift2JavaGenerator {
193193
}
194194

195195
private func printEnumHelpers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
196-
// TODO: Move this to seperate file +Enum?
197196
printEnumDiscriminator(&printer, decl)
198197
printer.println()
199198
printEnumCaseInterface(&printer, decl)
@@ -212,7 +211,8 @@ extension JNISwift2JavaGenerator {
212211
}
213212

214213
private func printEnumCaseInterface(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
215-
// printer.print("public sealed interface Case {}")
214+
printer.print("public sealed interface Case {}")
215+
// TODO: Print `getCase()` method to allow for easy pattern matching.
216216
}
217217

218218
private func printEnumStaticInitializers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
@@ -222,11 +222,54 @@ extension JNISwift2JavaGenerator {
222222
}
223223

224224
private func printEnumCases(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
225-
// for enumCase in decl.cases {
226-
// printer.printBraceBlock("public static final \(enumCase.name.firstCharacterUppercased) implements Case") { printer in
227-
//
228-
// }
229-
// }
225+
for enumCase in decl.cases {
226+
guard let translatedCase = self.translatedEnumCase(for: enumCase) else {
227+
return
228+
}
229+
230+
let members = translatedCase.translatedValues.map {
231+
$0.parameter.renderParameter()
232+
}
233+
234+
let caseName = enumCase.name.firstCharacterUppercased
235+
let hasParameters = !enumCase.parameters.isEmpty
236+
237+
// Print record
238+
printer.printBraceBlock("public record \(caseName)(\(members.joined(separator: ", "))) implements Case") { printer in
239+
if hasParameters {
240+
let nativeResults = zip(translatedCase.translatedValues, translatedCase.conversions).map { value, conversion in
241+
"\(conversion.native.javaType) \(value.parameter.name)"
242+
}
243+
printer.print(#"@SuppressWarnings("unused")"#)
244+
printer.printBraceBlock("static \(caseName) fromJNI(\(nativeResults.joined(separator: ", ")))") { printer in
245+
let memberValues = zip(translatedCase.translatedValues, translatedCase.conversions).map { (value, conversion) in
246+
let result = conversion.translated.conversion.render(&printer, value.parameter.name)
247+
return result
248+
}
249+
printer.print("return new \(caseName)(\(memberValues.joined(separator: ", ")));")
250+
}
251+
}
252+
}
253+
254+
// TODO: Optimize when all values can just be passed directly, instead of going through "middle type"?
255+
256+
// Print method to get enum as case
257+
printer.printBraceBlock("public Optional<\(caseName)> getAs\(caseName)()") { printer in
258+
// TODO: Check that discriminator is OK
259+
if hasParameters {
260+
printer.print(
261+
"""
262+
return Optional.of($getAs\(caseName)(this.$memoryAddress()));
263+
"""
264+
)
265+
} else {
266+
printer.print("return Optional.of(new \(caseName)());")
267+
}
268+
}
269+
printer.print("private static native \(caseName) $getAs\(caseName)(long self);")
270+
271+
printer.println()
272+
}
230273
}
231274

232275
private func printFunctionDowncallMethods(

0 commit comments

Comments
 (0)