Skip to content

Commit 92cbb5e

Browse files
committed
[JExtract/FFM] Trasnlate Optional parameters
1 parent 870e182 commit 92cbb5e

File tree

11 files changed

+348
-24
lines changed

11 files changed

+348
-24
lines changed

Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,23 @@ public func globalReceiveSomeDataProtocol(data: some DataProtocol) -> Int {
6868
return data.count
6969
}
7070

71+
public func globalReceiveOptional(o1: Int?, o2: (some DataProtocol)?) -> Int {
72+
switch (o1, o2) {
73+
case (nil, nil):
74+
p("<nil>, <nil>")
75+
return 0
76+
case (let v1?, nil):
77+
p("\(v1), <nil>")
78+
return 1
79+
case (nil, let v2?):
80+
p("<nil>, \(v2)")
81+
return 2
82+
case (let v1?, let v2?):
83+
p("\(v1), \(v2)")
84+
return 3
85+
}
86+
}
87+
7188
// ==== Internal helpers
7289

7390
func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) {

Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
import org.swift.swiftkit.ffm.AllocatingSwiftArena;
2323
import org.swift.swiftkit.ffm.SwiftRuntime;
2424

25+
import java.util.Optional;
26+
import java.util.OptionalLong;
27+
2528
public class HelloJava2Swift {
2629

2730
public static void main(String[] args) {
@@ -95,6 +98,7 @@ static void examples() {
9598
var bytes = arena.allocateFrom("hello");
9699
var dat = Data.init(bytes, bytes.byteSize(), arena);
97100
MySwiftLibrary.globalReceiveSomeDataProtocol(dat);
101+
MySwiftLibrary.globalReceiveOptional(OptionalLong.of(12), Optional.of(dat));
98102
}
99103

100104

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
package com.example.swift;
16+
17+
import org.junit.jupiter.api.Test;
18+
import org.swift.swiftkit.ffm.AllocatingSwiftArena;
19+
20+
import java.util.Optional;
21+
import java.util.OptionalLong;
22+
23+
import static org.junit.jupiter.api.Assertions.*;
24+
25+
public class OptionalImportTest {
26+
@Test
27+
void test_Optional_receive() {
28+
try (var arena = AllocatingSwiftArena.ofConfined()) {
29+
var origBytes = arena.allocateFrom("foobar");
30+
var data = Data.init(origBytes, origBytes.byteSize(), arena);
31+
assertEquals(0, MySwiftLibrary.globalReceiveOptional(OptionalLong.empty(), Optional.empty()));
32+
assertEquals(3, MySwiftLibrary.globalReceiveOptional(OptionalLong.of(12), Optional.of(data)));
33+
}
34+
}
35+
}

Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ struct CdeclLowering {
194194
)
195195

196196
case .unsafeBufferPointer, .unsafeMutableBufferPointer:
197-
guard let genericArgs = type.asNominalType?.genericArguments, genericArgs.count == 1 else {
197+
guard let genericArgs = nominal.genericArguments, genericArgs.count == 1 else {
198198
throw LoweringError.unhandledType(type)
199199
}
200200
// Typed pointers are lowered to (raw-pointer, count) pair.
@@ -254,7 +254,7 @@ struct CdeclLowering {
254254
))
255255

256256
case .optional:
257-
guard let genericArgs = type.asNominalType?.genericArguments, genericArgs.count == 1 else {
257+
guard let genericArgs = nominal.genericArguments, genericArgs.count == 1 else {
258258
throw LoweringError.unhandledType(type)
259259
}
260260
return try lowerOptionalParameter(genericArgs[0], convention: convention, parameterName: parameterName)

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,6 @@ extension FFMSwift2JavaGenerator {
238238
throw JavaTranslationError.unhandledType(type)
239239
}
240240

241-
242241
/// Translate a Swift API signature to the user-facing Java API signature.
243242
///
244243
/// Note that the result signature is for the high-level Java API, not the
@@ -349,6 +348,12 @@ extension FFMSwift2JavaGenerator {
349348
])
350349
)
351350

351+
case .optional:
352+
guard let genericArgs = swiftNominalType.genericArguments, genericArgs.count == 1 else {
353+
throw JavaTranslationError.unhandledType(swiftType)
354+
}
355+
return try translateOptionalParameter(wrappedType: genericArgs[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName)
356+
352357
case .string:
353358
return TranslatedParameter(
354359
javaParameters: [
@@ -414,8 +419,81 @@ extension FFMSwift2JavaGenerator {
414419
// Otherwise, not supported yet.
415420
throw JavaTranslationError.unhandledType(swiftType)
416421

417-
case .optional:
418-
throw JavaTranslationError.unhandledType(swiftType)
422+
case .optional(let wrapped):
423+
return try translateOptionalParameter(wrappedType: wrapped, convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName)
424+
}
425+
}
426+
427+
/// Translate an Optional Swift API parameter to the user-facing Java API parameter.
428+
func translateOptionalParameter(
429+
wrappedType swiftType: SwiftType,
430+
convention: SwiftParameterConvention,
431+
parameterName: String,
432+
loweredParam: LoweredParameter,
433+
methodName: String
434+
) throws -> TranslatedParameter {
435+
// If there is a 1:1 mapping between this Swift type and a C type, that can
436+
// be expressed as a Java primitive type.
437+
if let cType = try? CType(cdeclType: swiftType) {
438+
var (translatedClass, lowerFunc) = switch cType.javaType {
439+
case .int: ("OptionalInt", "toOptionalSegmentInt")
440+
case .long: ("OptionalLong", "toOptionalSegmentLong")
441+
case .double: ("OptionalDouble", "toOptionalSegmentDouble")
442+
case .boolean: ("Optional<Boolean>", "toOptionalSegmentBoolean")
443+
case .byte: ("Optional<Byte>", "toOptionalSegmentByte")
444+
case .char: ("Optional<Character>", "toOptionalSegmentCharacter")
445+
case .short: ("Optional<Short>", "toOptionalSegmentShort")
446+
case .float: ("Optional<Float>", "toOptionalSegmentFloat")
447+
default:
448+
throw JavaTranslationError.unhandledType(.optional(swiftType))
449+
}
450+
return TranslatedParameter(
451+
javaParameters: [
452+
JavaParameter(name: parameterName, type: JavaType(className: translatedClass))
453+
],
454+
conversion: .call(.placeholder, function: "SwiftRuntime.\(lowerFunc)", withArena: true)
455+
)
456+
}
457+
458+
switch swiftType {
459+
case .nominal(let nominal):
460+
if let knownType = nominal.nominalTypeDecl.knownTypeKind {
461+
switch knownType {
462+
case .data, .dataProtocol:
463+
break
464+
default:
465+
throw JavaTranslationError.unhandledType(.optional(swiftType))
466+
}
467+
}
468+
469+
let translatedTy = try self.translate(swiftType: swiftType)
470+
return TranslatedParameter(
471+
javaParameters: [
472+
JavaParameter(name: parameterName, type: JavaType(className: "Optional<\(translatedTy.description)>"))
473+
],
474+
conversion: .call(.placeholder, function: "SwiftRuntime.toOptionalSegmentInstance", withArena: false)
475+
)
476+
case .existential(let proto), .opaque(let proto):
477+
if
478+
let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind,
479+
let concreteTy = knownTypes.representativeType(of: knownProtocol)
480+
{
481+
return try translateOptionalParameter(
482+
wrappedType: concreteTy,
483+
convention: convention,
484+
parameterName: parameterName,
485+
loweredParam: loweredParam,
486+
methodName: methodName
487+
)
488+
}
489+
throw JavaTranslationError.unhandledType(.optional(swiftType))
490+
case .tuple(let tuple):
491+
if tuple.count == 1 {
492+
return try translateOptionalParameter(wrappedType: tuple[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName)
493+
}
494+
throw JavaTranslationError.unhandledType(.optional(swiftType))
495+
default:
496+
throw JavaTranslationError.unhandledType(.optional(swiftType))
419497
}
420498
}
421499

@@ -585,7 +663,6 @@ extension FFMSwift2JavaGenerator.TranslatedFunctionSignature {
585663
}
586664
}
587665

588-
589666
extension CType {
590667
/// Map lowered C type to Java type for FFM binding.
591668
var javaType: JavaType {

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,7 @@ extension FFMSwift2JavaGenerator {
101101
// Necessary for native calls and type mapping
102102
"java.lang.foreign.*",
103103
"java.lang.invoke.*",
104-
"java.util.Arrays",
105-
"java.util.stream.Collectors",
106-
"java.util.concurrent.atomic.*",
104+
"java.util.*",
107105
"java.nio.charset.StandardCharsets",
108106
]
109107
}

SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,25 @@
1717
import java.util.concurrent.atomic.AtomicBoolean;
1818

1919
public abstract class JNISwiftInstance extends SwiftInstance {
20+
/// Pointer to the "self".
21+
private final long selfPointer;
22+
23+
/**
24+
* The pointer to the instance in memory. I.e. the {@code self} of the Swift object or value.
25+
*/
26+
public final long pointer() {
27+
return this.selfPointer;
28+
}
29+
2030
/**
2131
* The designated constructor of any imported Swift types.
2232
*
2333
* @param pointer a pointer to the memory containing the value
2434
* @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed.
2535
*/
2636
protected JNISwiftInstance(long pointer, SwiftArena arena) {
27-
super(pointer, arena);
37+
super(arena);
38+
this.selfPointer = pointer;
2839
}
2940

3041
/**

SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,6 @@
1717
import java.util.concurrent.atomic.AtomicBoolean;
1818

1919
public abstract class SwiftInstance {
20-
/// Pointer to the "self".
21-
private final long selfPointer;
22-
23-
/**
24-
* The pointer to the instance in memory. I.e. the {@code self} of the Swift object or value.
25-
*/
26-
public final long pointer() {
27-
return this.selfPointer;
28-
}
2920

3021
/**
3122
* Called when the arena has decided the value should be destroyed.
@@ -55,8 +46,7 @@ public final long pointer() {
5546
* @param pointer a pointer to the memory containing the value
5647
* @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed.
5748
*/
58-
protected SwiftInstance(long pointer, SwiftArena arena) {
59-
this.selfPointer = pointer;
49+
protected SwiftInstance(SwiftArena arena) {
6050
arena.register(this);
6151
}
6252

SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public abstract class FFMSwiftInstance extends SwiftInstance {
4141
* @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed.
4242
*/
4343
protected FFMSwiftInstance(MemorySegment segment, AllocatingSwiftArena arena) {
44-
super(segment.address(), arena);
44+
super(arena);
4545
this.memorySegment = segment;
4646
}
4747

SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414

1515
package org.swift.swiftkit.ffm;
1616

17+
import org.swift.swiftkit.core.SwiftInstance;
1718
import org.swift.swiftkit.core.util.PlatformUtils;
1819

1920
import java.lang.foreign.*;
2021
import java.lang.invoke.MethodHandle;
2122
import java.lang.invoke.MethodHandles;
2223
import java.lang.invoke.VarHandle;
23-
import java.util.Arrays;
24-
import java.util.Optional;
24+
import java.util.*;
2525
import java.util.stream.Collectors;
2626

2727
import static org.swift.swiftkit.core.util.StringUtils.stripPrefix;
@@ -425,6 +425,42 @@ public static MemorySegment toCString(String str, Arena arena) {
425425
return arena.allocateFrom(str);
426426
}
427427

428+
public static MemorySegment toOptionalSegmentInt(OptionalInt opt, Arena arena) {
429+
return opt.isPresent() ? arena.allocateFrom(ValueLayout.JAVA_INT, opt.getAsInt()) : MemorySegment.NULL;
430+
}
431+
432+
public static MemorySegment toOptionalSegmentLong(OptionalLong opt, Arena arena) {
433+
return opt.isPresent() ? arena.allocateFrom(ValueLayout.JAVA_LONG, opt.getAsLong()) : MemorySegment.NULL;
434+
}
435+
436+
public static MemorySegment toOptionalSegmentDouble(OptionalDouble opt, Arena arena) {
437+
return opt.isPresent() ? arena.allocateFrom(ValueLayout.JAVA_DOUBLE, opt.getAsDouble()) : MemorySegment.NULL;
438+
}
439+
440+
public static MemorySegment toOptionalSegmentBoolean(Optional<Boolean> opt, Arena arena) {
441+
return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_BYTE, (byte) (val ? 1 : 0))).orElse(MemorySegment.NULL);
442+
}
443+
444+
public static MemorySegment toOptionalSegmentByte(Optional<Byte> opt, Arena arena) {
445+
return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_BYTE, val)).orElse(MemorySegment.NULL);
446+
}
447+
448+
public static MemorySegment toOptionalSegmentCharacter(Optional<Character> opt, Arena arena) {
449+
return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_CHAR, val)).orElse(MemorySegment.NULL);
450+
}
451+
452+
public static MemorySegment toOptionalSegmentShort(Optional<Short> opt, Arena arena) {
453+
return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_SHORT, val)).orElse(MemorySegment.NULL);
454+
}
455+
456+
public static MemorySegment toOptionalSegmentFloat(Optional<Float> opt, Arena arena) {
457+
return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_FLOAT, val)).orElse(MemorySegment.NULL);
458+
}
459+
460+
public static <Instance extends FFMSwiftInstance> MemorySegment toOptionalSegmentInstance(Optional<Instance> opt) {
461+
return opt.map(instance -> instance.$memorySegment()).orElse(MemorySegment.NULL);
462+
}
463+
428464
private static class swift_getTypeName {
429465

430466
/**

0 commit comments

Comments
 (0)