Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import JavaKit

public func optionalBool(input: Optional<Bool>) -> Bool? {
return input
}

public func optionalByte(input: Optional<Int8>) -> Int8? {
return input
}

public func optionalChar(input: Optional<UInt16>) -> UInt16? {
return input
}

public func optionalShort(input: Optional<Int16>) -> Int16? {
return input
}

public func optionalInt(input: Optional<Int32>) -> Int32? {
return input
}

public func optionalLong(input: Optional<Int64>) -> Int64? {
return input
}

public func optionalFloat(input: Optional<Float>) -> Float? {
return input
}

public func optionalDouble(input: Optional<Double>) -> Double? {
return input
}

public func optionalString(input: Optional<String>) -> String? {
return input
}

public func optionalClass(input: Optional<MySwiftClass>) -> MySwiftClass? {
return input
}

public func optionalJavaKitLong(input: Optional<JavaLong>) -> Int64? {
if let input {
return input.longValue()
} else {
return nil
}
}

public func multipleOptionals(
input1: Optional<Int8>,
input2: Optional<Int16>,
input3: Optional<Int32>,
input4: Optional<Int64>,
input5: Optional<String>,
input6: Optional<MySwiftClass>,
input7: Optional<Bool>
) -> Int64? {
return 1
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
import org.junit.jupiter.api.Test;
import org.swift.swiftkit.core.ConfinedSwiftMemorySession;

import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;

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

public class MySwiftClassTest {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

package com.example.swift;

import org.junit.jupiter.api.Test;
import org.swift.swiftkit.core.ConfinedSwiftMemorySession;

import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class OptionalsTest {
@Test
void optionalBool() {
assertEquals(Optional.empty(), MySwiftLibrary.optionalBool(Optional.empty()));
assertEquals(Optional.of(true), MySwiftLibrary.optionalBool(Optional.of(true)));
}

@Test
void optionalByte() {
assertEquals(Optional.empty(), MySwiftLibrary.optionalByte(Optional.empty()));
assertEquals(Optional.of((byte) 1) , MySwiftLibrary.optionalByte(Optional.of((byte) 1)));
}

@Test
void optionalChar() {
assertEquals(Optional.empty(), MySwiftLibrary.optionalChar(Optional.empty()));
assertEquals(Optional.of((char) 42), MySwiftLibrary.optionalChar(Optional.of((char) 42)));
}

@Test
void optionalShort() {
assertEquals(Optional.empty(), MySwiftLibrary.optionalShort(Optional.empty()));
assertEquals(Optional.of((short) -250), MySwiftLibrary.optionalShort(Optional.of((short) -250)));
}

@Test
void optionalInt() {
assertEquals(OptionalInt.empty(), MySwiftLibrary.optionalInt(OptionalInt.empty()));
assertEquals(OptionalInt.of(999), MySwiftLibrary.optionalInt(OptionalInt.of(999)));
}

@Test
void optionalLong() {
assertEquals(OptionalLong.empty(), MySwiftLibrary.optionalLong(OptionalLong.empty()));
assertEquals(OptionalLong.of(999), MySwiftLibrary.optionalLong(OptionalLong.of(999)));
}

@Test
void optionalFloat() {
assertEquals(Optional.empty(), MySwiftLibrary.optionalFloat(Optional.empty()));
assertEquals(Optional.of(3.14f), MySwiftLibrary.optionalFloat(Optional.of(3.14f)));
}

@Test
void optionalDouble() {
assertEquals(OptionalDouble.empty(), MySwiftLibrary.optionalDouble(OptionalDouble.empty()));
assertEquals(OptionalDouble.of(2.718), MySwiftLibrary.optionalDouble(OptionalDouble.of(2.718)));
}

@Test
void optionalString() {
assertEquals(Optional.empty(), MySwiftLibrary.optionalString(Optional.empty()));
assertEquals(Optional.of("Hello Swift!"), MySwiftLibrary.optionalString(Optional.of("Hello Swift!")));
}

@Test
void optionalClass() {
try (var arena = new ConfinedSwiftMemorySession()) {
MySwiftClass c = MySwiftClass.init(arena);
assertEquals(Optional.empty(), MySwiftLibrary.optionalClass(Optional.empty(), arena));
Optional<MySwiftClass> optionalClass = MySwiftLibrary.optionalClass(Optional.of(c), arena);
assertTrue(optionalClass.isPresent());
assertEquals(c.getX(), optionalClass.get().getX());
}
}

@Test
void optionalJavaKitLong() {
assertEquals(OptionalLong.empty(), MySwiftLibrary.optionalJavaKitLong(Optional.empty()));
assertEquals(OptionalLong.of(99L), MySwiftLibrary.optionalJavaKitLong(Optional.of(99L)));
}

@Test
void multipleOptionals() {
try (var arena = new ConfinedSwiftMemorySession()) {
MySwiftClass c = MySwiftClass.init(arena);
OptionalLong result = MySwiftLibrary.multipleOptionals(
Optional.of((byte) 1),
Optional.of((short) 42),
OptionalInt.of(50),
OptionalLong.of(1000L),
Optional.of("42"),
Optional.of(c),
Optional.of(true)
);
assertEquals(result, OptionalLong.of(1L));
}
}
}
75 changes: 75 additions & 0 deletions Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,79 @@ extension JavaType {
case .void: fatalError("There is no type signature for 'void'")
}
}

/// Returns the next integral type with space for self and an additional byte.
var nextIntergralTypeWithSpaceForByte: (javaType: JavaType, swiftType: SwiftKnownTypeDeclKind, valueBytes: Int)? {
switch self {
case .boolean, .byte: (.short, .int16, 1)
case .char, .short: (.int, .int32, 2)
case .int: (.long, .int64, 4)
default: nil
}
}

var optionalType: String? {
switch self {
case .boolean: "Optional<Boolean>"
case .byte: "Optional<Byte>"
case .char: "Optional<Character>"
case .short: "Optional<Short>"
case .int: "OptionalInt"
case .long: "OptionalLong"
case .float: "Optional<Float>"
case .double: "OptionalDouble"
case .javaLangString: "Optional<String>"
default: nil
}
}

var optionalWrapperType: String? {
switch self {
case .boolean, .byte, .char, .short, .float, .javaLangString: "Optional"
case .int: "OptionalInt"
case .long: "OptionalLong"
case .double: "OptionalDouble"
default: nil
}
}

var optionalPlaceholderValue: String? {
switch self {
case .boolean: "false"
case .byte: "(byte) 0"
case .char: "(char) 0"
case .short: "(short) 0"
case .int: "0"
case .long: "0L"
case .float: "0f"
case .double: "0.0"
case .array, .class: "null"
case .void: nil
}
}

var jniCallMethodAName: String {
switch self {
case .boolean: "CallBooleanMethodA"
case .byte: "CallByteMethodA"
case .char: "CallCharMethodA"
case .short: "CallShortMethodA"
case .int: "CallIntMethodA"
case .long: "CallLongMethodA"
case .float: "CallFloatMethodA"
case .double: "CallDoubleMethodA"
case .void: "CallVoidMethodA"
default: "CallObjectMethodA"
}
}

/// Returns whether this type returns `JavaValue` from JavaKit
var implementsJavaValue: Bool {
return switch self {
case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString:
true
default:
false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//

import JavaTypes

// MARK: Defaults

Expand All @@ -20,6 +21,7 @@ extension JNISwift2JavaGenerator {
static let defaultJavaImports: Array<String> = [
"org.swift.swiftkit.core.*",
"org.swift.swiftkit.core.util.*",
"java.util.*",

// NonNull, Unsigned and friends
"org.swift.swiftkit.core.annotations.*",
Expand Down Expand Up @@ -276,11 +278,15 @@ extension JNISwift2JavaGenerator {
let translatedDecl = translatedDecl(for: decl)! // Will always call with valid decl
let nativeSignature = translatedDecl.nativeFunctionSignature
let resultType = nativeSignature.result.javaType
var parameters = nativeSignature.parameters
if let selfParameter = nativeSignature.selfParameter {
parameters.append(selfParameter)
var parameters = nativeSignature.parameters.flatMap(\.parameters)
if let selfParameter = nativeSignature.selfParameter?.parameters {
parameters += selfParameter
}
let renderedParameters = parameters.map { "\($0.javaType) \($0.name)"}.joined(separator: ", ")
parameters += nativeSignature.result.outParameters

let renderedParameters = parameters.map { javaParameter in
"\(javaParameter.type) \(javaParameter.name)"
}.joined(separator: ", ")

printer.print("private static native \(resultType) \(translatedDecl.nativeFunctionName)(\(renderedParameters));")
}
Expand All @@ -305,6 +311,12 @@ extension JNISwift2JavaGenerator {
arguments.append(lowered)
}

// Indirect return receivers
for outParameter in translatedFunctionSignature.resultType.outParameters {
printer.print("\(outParameter.type) \(outParameter.name) = \(outParameter.allocation.render());")
arguments.append(outParameter.name)
}

//=== Part 3: Downcall.
// TODO: If we always generate a native method and a "public" method, we can actually choose our own thunk names
// using the registry?
Expand Down
Loading
Loading