Skip to content

Commit dc619ae

Browse files
committed
add support for basic primitive types as optional returns
1 parent 7cba57b commit dc619ae

File tree

5 files changed

+262
-42
lines changed

5 files changed

+262
-42
lines changed

Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,31 @@ public class MySwiftClass {
9191
return self.x + other.longValue()
9292
}
9393

94-
public func optionalMethod(input: Optional<Int32>) -> Int64 {
95-
if let input {
96-
return Int64(input)
97-
} else {
98-
return 0
99-
}
94+
public func optionalBool(input: Optional<Bool>) -> Bool? {
95+
return input
10096
}
10197

102-
public func optionalMethodClass(input: MySwiftClass?) -> Bool {
103-
if let input {
104-
return true
105-
} else {
106-
return false
107-
}
98+
public func optionalByte(input: Optional<Int8>) -> Int8? {
99+
return input
108100
}
101+
102+
public func optionalChar(input: Optional<UInt16>) -> UInt16? {
103+
return input
104+
}
105+
106+
public func optionalShort(input: Optional<Int16>) -> Int16? {
107+
return input
108+
}
109+
110+
public func optionalInt(input: Optional<Int32>) -> Int32? {
111+
return input
112+
}
113+
114+
// public func optionalMethodClass(input: MySwiftClass?) -> <Optional<MySwiftClass>> {
115+
// if let input {
116+
// return MySwiftClass(x: input.x * 10, y: input.y * 10)
117+
// } else {
118+
// return nil
119+
// }
120+
// }
109121
}

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

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.util.Optional;
2121
import java.util.OptionalInt;
22+
import java.util.OptionalLong;
2223

2324
import static org.junit.jupiter.api.Assertions.*;
2425

@@ -153,21 +154,57 @@ void addXWithJavaLong() {
153154
}
154155

155156
@Test
156-
void optionalMethod() {
157+
void optionalBool() {
157158
try (var arena = new ConfinedSwiftMemorySession()) {
158159
MySwiftClass c1 = MySwiftClass.init(20, 10, arena);
159-
assertEquals(0, c1.optionalMethod(OptionalInt.empty()));
160-
assertEquals(50, c1.optionalMethod(OptionalInt.of(50)));
160+
assertEquals(Optional.empty(), c1.optionalBool(Optional.empty()));
161+
assertEquals(Optional.of(true), c1.optionalBool(Optional.of(true)));
161162
}
162163
}
163164

164165
@Test
165-
void optionalMethodClass() {
166+
void optionalByte() {
166167
try (var arena = new ConfinedSwiftMemorySession()) {
167168
MySwiftClass c1 = MySwiftClass.init(20, 10, arena);
168-
MySwiftClass c2 = MySwiftClass.init(50, 10, arena);
169-
assertFalse(c1.optionalMethodClass(Optional.empty()));
170-
assertTrue(c1.optionalMethodClass(Optional.of(c2)));
169+
assertEquals(Optional.empty(), c1.optionalByte(Optional.empty()));
170+
assertEquals(Optional.of((byte) 1) , c1.optionalByte(Optional.of((byte) 1)));
171+
}
172+
}
173+
174+
@Test
175+
void optionalChar() {
176+
try (var arena = new ConfinedSwiftMemorySession()) {
177+
MySwiftClass c1 = MySwiftClass.init(20, 10, arena);
178+
assertEquals(Optional.empty(), c1.optionalChar(Optional.empty()));
179+
assertEquals(Optional.of((char) 42), c1.optionalChar(Optional.of((char) 42)));
180+
}
181+
}
182+
183+
@Test
184+
void optionalShort() {
185+
try (var arena = new ConfinedSwiftMemorySession()) {
186+
MySwiftClass c1 = MySwiftClass.init(20, 10, arena);
187+
assertEquals(Optional.empty(), c1.optionalShort(Optional.empty()));
188+
assertEquals(Optional.of((short) -250), c1.optionalShort(Optional.of((short) -250)));
189+
}
190+
}
191+
192+
@Test
193+
void optionalInt() {
194+
try (var arena = new ConfinedSwiftMemorySession()) {
195+
MySwiftClass c1 = MySwiftClass.init(20, 10, arena);
196+
assertEquals(OptionalInt.empty(), c1.optionalInt(OptionalInt.empty()));
197+
assertEquals(OptionalInt.of(999), c1.optionalInt(OptionalInt.of(999)));
171198
}
172199
}
200+
201+
// @Test
202+
// void optionalMethodClass() {
203+
// try (var arena = new ConfinedSwiftMemorySession()) {
204+
// MySwiftClass c1 = MySwiftClass.init(20, 10, arena);
205+
// MySwiftClass c2 = MySwiftClass.init(50, 10, arena);
206+
// assertFalse(c1.optionalMethodClass(Optional.empty()));
207+
// assertTrue(c1.optionalMethodClass(Optional.of(c2)));
208+
// }
209+
// }
173210
}

Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,14 @@ extension JavaType {
3636
case .void: fatalError("There is no type signature for 'void'")
3737
}
3838
}
39+
40+
/// Returns the next integral type with space for self and an additional byte.
41+
var nextIntergralTypeWithSpaceForByte: (java: JavaType, swift: String, valueBytes: Int)? {
42+
switch self {
43+
case .boolean, .byte: (.short, "Int16", 1)
44+
case .char, .short: (.int, "Int32", 2)
45+
case .int: (.long, "Int64", 4)
46+
default: nil
47+
}
48+
}
3949
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift

Lines changed: 105 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,11 @@ extension JNISwift2JavaGenerator {
254254

255255
let (translatedClass, orElseValue) = switch javaType {
256256
case .boolean: ("Optional<Boolean>", "false")
257-
case .byte: ("Optional<Byte>", "0")
258-
case .char: ("Optional<Character>", "0")
259-
case .short: ("Optional<Short>", "0")
257+
case .byte: ("Optional<Byte>", "(byte) 0")
258+
case .char: ("Optional<Character>", "(char) 0")
259+
case .short: ("Optional<Short>", "(short) 0")
260260
case .int: ("OptionalInt", "0")
261-
case .long: ("OptionalLong", "0")
261+
case .long: ("OptionalLong", "0L")
262262
case .float: ("Optional<Float>", "0")
263263
case .double: ("OptionalDouble", "0")
264264
case .javaLangString: ("Optional<String>", #""""#)
@@ -302,17 +302,28 @@ extension JNISwift2JavaGenerator {
302302
func translate(
303303
swiftResult: SwiftResult
304304
) throws -> TranslatedResult {
305-
switch swiftResult.type {
305+
let swiftType = swiftResult.type
306+
307+
switch swiftType {
306308
case .nominal(let nominalType):
307309
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
308-
guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else {
309-
throw JavaTranslationError.unsupportedSwiftType(swiftResult.type)
310-
}
310+
switch knownType {
311+
case .optional:
312+
guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else {
313+
throw JavaTranslationError.unsupportedSwiftType(swiftType)
314+
}
315+
return try translateOptionalResult(wrappedType: genericArgs[0])
311316

312-
return TranslatedResult(
313-
javaType: javaType,
314-
conversion: .placeholder
315-
)
317+
default:
318+
guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else {
319+
throw JavaTranslationError.unsupportedSwiftType(swiftResult.type)
320+
}
321+
322+
return TranslatedResult(
323+
javaType: javaType,
324+
conversion: .placeholder
325+
)
326+
}
316327
}
317328

318329
if nominalType.isJavaKitWrapper {
@@ -329,10 +340,70 @@ extension JNISwift2JavaGenerator {
329340
case .tuple([]):
330341
return TranslatedResult(javaType: .void, conversion: .placeholder)
331342

332-
case .metatype, .optional, .tuple, .function, .existential, .opaque:
343+
case .optional(let wrapped):
344+
return try translateOptionalResult(wrappedType: wrapped)
345+
346+
case .metatype, .tuple, .function, .existential, .opaque:
333347
throw JavaTranslationError.unsupportedSwiftType(swiftResult.type)
334348
}
335349
}
350+
351+
func translateOptionalResult(
352+
wrappedType swiftType: SwiftType
353+
) throws -> TranslatedResult {
354+
switch swiftType {
355+
case .nominal(let nominalType):
356+
let nominalTypeName = nominalType.nominalTypeDecl.name
357+
358+
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
359+
guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else {
360+
throw JavaTranslationError.unsupportedSwiftType(swiftType)
361+
}
362+
363+
let (returnType, staticCallee) = switch javaType {
364+
case .boolean: ("Optional<Boolean>", "Optional")
365+
case .byte: ("Optional<Byte>", "Optional")
366+
case .char: ("Optional<Character>", "Optional")
367+
case .short: ("Optional<Short>", "Optional")
368+
case .int: ("OptionalInt", "OptionalInt")
369+
case .long: ("OptionalLong", "OptionalLong")
370+
case .float: ("Optional<Float>", "Optional")
371+
case .double: ("OptionalDouble", "OptionalDouble")
372+
case .javaLangString: ("Optional<String>", "Optional")
373+
default:
374+
throw JavaTranslationError.unsupportedSwiftType(swiftType)
375+
}
376+
377+
// Check if we can fit the value and a discriminator byte in a primitive.
378+
// so the return JNI value will be (value || discriminator)
379+
if let nextIntergralTypeWithSpaceForByte = javaType.nextIntergralTypeWithSpaceForByte {
380+
return TranslatedResult(
381+
javaType: .class(package: nil, name: returnType),
382+
conversion: .combinedValueToOptional(
383+
.placeholder,
384+
nextIntergralTypeWithSpaceForByte.java,
385+
valueType: javaType,
386+
valueSizeInBytes: nextIntergralTypeWithSpaceForByte.valueBytes,
387+
optionalType: staticCallee
388+
)
389+
)
390+
} else {
391+
// Otherwise, we are forced to use an array.
392+
fatalError()
393+
}
394+
}
395+
396+
guard !nominalType.isJavaKitWrapper else {
397+
throw JavaTranslationError.unsupportedSwiftType(swiftType)
398+
}
399+
400+
// Assume JExtract imported class
401+
throw JavaTranslationError.unsupportedSwiftType(swiftType)
402+
403+
default:
404+
throw JavaTranslationError.unsupportedSwiftType(swiftType)
405+
}
406+
}
336407
}
337408

338409
struct TranslatedFunctionDecl {
@@ -433,6 +504,8 @@ extension JNISwift2JavaGenerator {
433504

434505
case isOptionalPresent
435506

507+
indirect case combinedValueToOptional(JavaNativeConversionStep, JavaType, valueType: JavaType, valueSizeInBytes: Int, optionalType: String)
508+
436509
/// Returns the conversion string applied to the placeholder.
437510
func render(_ printer: inout CodePrinter, _ placeholder: String) -> String {
438511
// NOTE: 'printer' is used if the conversion wants to cause side-effects.
@@ -466,6 +539,22 @@ extension JNISwift2JavaGenerator {
466539
let argsStr = args.joined(separator: ", ")
467540
return "\(inner).\(methodName)(\(argsStr))"
468541

542+
case .combinedValueToOptional(let combined, let combinedType, let valueType, let valueSizeInBytes, let optionalType):
543+
let combined = combined.render(&printer, placeholder)
544+
printer.print(
545+
"""
546+
\(combinedType) combined$ = \(combined);
547+
byte discriminator$ = (byte) (combined$ & 0xFF);
548+
"""
549+
)
550+
551+
if valueType == .boolean {
552+
printer.print("boolean value$ = ((byte) (combined$ >> 8)) != 0;")
553+
} else {
554+
printer.print("\(valueType) value$ = (\(valueType)) (combined$ >> \(valueSizeInBytes * 8));")
555+
}
556+
557+
return "discriminator$ == 1 ? \(optionalType).of(value$) : \(optionalType).empty()"
469558
}
470559
}
471560

@@ -486,6 +575,9 @@ extension JNISwift2JavaGenerator {
486575

487576
case .method(let inner, _, _):
488577
return inner.requiresSwiftArena
578+
579+
case .combinedValueToOptional(let inner, _, _, _, _):
580+
return inner.requiresSwiftArena
489581
}
490582
}
491583
}

0 commit comments

Comments
 (0)