Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,23 @@ public func globalReceiveSomeDataProtocol(data: some DataProtocol) -> Int {
return data.count
}

public func globalReceiveOptional(o1: Int?, o2: (some DataProtocol)?) -> Int {
switch (o1, o2) {
case (nil, nil):
p("<nil>, <nil>")
return 0
case (let v1?, nil):
p("\(v1), <nil>")
return 1
case (nil, let v2?):
p("<nil>, \(v2)")
return 2
case (let v1?, let v2?):
p("\(v1), \(v2)")
return 3
}
}

// ==== Internal helpers

func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import org.swift.swiftkit.ffm.AllocatingSwiftArena;
import org.swift.swiftkit.ffm.SwiftRuntime;

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

public class HelloJava2Swift {

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


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//===----------------------------------------------------------------------===//
//
// 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.ffm.AllocatingSwiftArena;

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

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

public class OptionalImportTest {
@Test
void test_Optional_receive() {
try (var arena = AllocatingSwiftArena.ofConfined()) {
var origBytes = arena.allocateFrom("foobar");
var data = Data.init(origBytes, origBytes.byteSize(), arena);
assertEquals(0, MySwiftLibrary.globalReceiveOptional(OptionalLong.empty(), Optional.empty()));
assertEquals(3, MySwiftLibrary.globalReceiveOptional(OptionalLong.of(12), Optional.of(data)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ extension SwiftKnownTypeDeclKind {
.qualified(const: true, volatile: false, type: .void)
)
case .void: .void
case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data, .dataProtocol:
case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data, .dataProtocol, .optional:
nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ struct CdeclLowering {
)

case .unsafeBufferPointer, .unsafeMutableBufferPointer:
guard let genericArgs = type.asNominalType?.genericArguments, genericArgs.count == 1 else {
guard let genericArgs = nominal.genericArguments, genericArgs.count == 1 else {
throw LoweringError.unhandledType(type)
}
// Typed pointers are lowered to (raw-pointer, count) pair.
Expand Down Expand Up @@ -253,6 +253,12 @@ struct CdeclLowering {
]
))

case .optional:
guard let genericArgs = nominal.genericArguments, genericArgs.count == 1 else {
throw LoweringError.unhandledType(type)
}
return try lowerOptionalParameter(genericArgs[0], convention: convention, parameterName: parameterName)

case .string:
// 'String' is passed in by C string. i.e. 'UnsafePointer<Int8>' ('const uint8_t *')
if knownType == .string {
Expand Down Expand Up @@ -336,8 +342,79 @@ struct CdeclLowering {
}
throw LoweringError.unhandledType(type)

case .optional:
throw LoweringError.unhandledType(type)
case .optional(let wrapped):
return try lowerOptionalParameter(wrapped, convention: convention, parameterName: parameterName)
}
}

/// Lower a Swift Optional to cdecl function type.
///
/// - Parameters:
/// - fn: the Swift function type to lower.
func lowerOptionalParameter(
_ wrappedType: SwiftType,
convention: SwiftParameterConvention,
parameterName: String
) throws -> LoweredParameter {
// If there is a 1:1 mapping between this Swift type and a C type, lower it to 'UnsafePointer<T>?'
if let _ = try? CType(cdeclType: wrappedType) {
return LoweredParameter(
cdeclParameters: [
SwiftParameter(convention: .byValue, parameterName: parameterName, type: .optional(knownTypes.unsafePointer(wrappedType)))
],
conversion: .pointee(.optionalChain(.placeholder))
)
}

switch wrappedType {
case .nominal(let nominal):
if let knownType = nominal.nominalTypeDecl.knownTypeKind {
switch knownType {
case .data:
break
case .unsafeRawPointer, .unsafeMutableRawPointer:
throw LoweringError.unhandledType(.optional(wrappedType))
case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer:
throw LoweringError.unhandledType(.optional(wrappedType))
case .unsafePointer, .unsafeMutablePointer:
throw LoweringError.unhandledType(.optional(wrappedType))
case .unsafeBufferPointer, .unsafeMutableBufferPointer:
throw LoweringError.unhandledType(.optional(wrappedType))
case .void, .string:
throw LoweringError.unhandledType(.optional(wrappedType))
case .dataProtocol:
throw LoweringError.unhandledType(.optional(wrappedType))
default:
// Unreachable? Should be handled by `CType(cdeclType:)` lowering above.
throw LoweringError.unhandledType(.optional(wrappedType))
}
}

// Lower arbitrary nominal to `UnsafeRawPointer?`
return LoweredParameter(
cdeclParameters: [
SwiftParameter(convention: .byValue, parameterName: parameterName, type: .optional(knownTypes.unsafeRawPointer))
],
conversion: .pointee(.typedPointer(.optionalChain(.placeholder), swiftType: wrappedType))
)

case .existential(let proto), .opaque(let proto):
if
let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind,
let concreteTy = knownTypes.representativeType(of: knownProtocol)
{
return try lowerOptionalParameter(concreteTy, convention: convention, parameterName: parameterName)
}
throw LoweringError.unhandledType(.optional(wrappedType))

case .tuple(let tuple):
if tuple.count == 1 {
return try lowerOptionalParameter(tuple[0], convention: convention, parameterName: parameterName)
}
throw LoweringError.unhandledType(.optional(wrappedType))

case .function, .metatype, .optional:
throw LoweringError.unhandledType(.optional(wrappedType))
}
}

Expand Down Expand Up @@ -527,13 +604,13 @@ struct CdeclLowering {
case .void:
return LoweredResult(cdeclResultType: .void, cdeclOutParameters: [], conversion: .placeholder)

case .string:
// Returning string is not supported at this point.
throw LoweringError.unhandledType(type)

case .data:
break

case .string, .optional:
// Not supported at this point.
throw LoweringError.unhandledType(type)

default:
// Unreachable? Should be handled by `CType(cdeclType:)` lowering above.
throw LoweringError.unhandledType(type)
Expand Down
8 changes: 7 additions & 1 deletion Sources/JExtractSwiftLib/FFM/ConversionStep.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ enum ConversionStep: Equatable {

indirect case member(ConversionStep, member: String)

indirect case optionalChain(ConversionStep)

/// Count the number of times that the placeholder occurs within this
/// conversion step.
var placeholderCount: Int {
Expand All @@ -71,7 +73,7 @@ enum ConversionStep: Equatable {
.typedPointer(let inner, swiftType: _),
.unsafeCastPointer(let inner, swiftType: _),
.populatePointer(name: _, assumingType: _, to: let inner),
.member(let inner, member: _):
.member(let inner, member: _), .optionalChain(let inner):
inner.placeholderCount
case .initialize(_, arguments: let arguments):
arguments.reduce(0) { $0 + $1.argument.placeholderCount }
Expand Down Expand Up @@ -175,6 +177,10 @@ enum ConversionStep: Equatable {
}
return nil

case .optionalChain(let step):
let inner = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems)
return ExprSyntax(OptionalChainingExprSyntax(expression: inner!))

case .closureLowering(let parameterSteps, let resultStep):
var body: [CodeBlockItemSyntax] = []

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,6 @@ extension FFMSwift2JavaGenerator {
throw JavaTranslationError.unhandledType(type)
}


/// Translate a Swift API signature to the user-facing Java API signature.
///
/// Note that the result signature is for the high-level Java API, not the
Expand Down Expand Up @@ -349,6 +348,12 @@ extension FFMSwift2JavaGenerator {
])
)

case .optional:
guard let genericArgs = swiftNominalType.genericArguments, genericArgs.count == 1 else {
throw JavaTranslationError.unhandledType(swiftType)
}
return try translateOptionalParameter(wrappedType: genericArgs[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName)

case .string:
return TranslatedParameter(
javaParameters: [
Expand Down Expand Up @@ -414,8 +419,81 @@ extension FFMSwift2JavaGenerator {
// Otherwise, not supported yet.
throw JavaTranslationError.unhandledType(swiftType)

case .optional:
throw JavaTranslationError.unhandledType(swiftType)
case .optional(let wrapped):
return try translateOptionalParameter(wrappedType: wrapped, convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName)
}
}

/// Translate an Optional Swift API parameter to the user-facing Java API parameter.
func translateOptionalParameter(
wrappedType swiftType: SwiftType,
convention: SwiftParameterConvention,
parameterName: String,
loweredParam: LoweredParameter,
methodName: String
) throws -> TranslatedParameter {
// If there is a 1:1 mapping between this Swift type and a C type, that can
// be expressed as a Java primitive type.
if let cType = try? CType(cdeclType: swiftType) {
var (translatedClass, lowerFunc) = switch cType.javaType {
case .int: ("OptionalInt", "toOptionalSegmentInt")
case .long: ("OptionalLong", "toOptionalSegmentLong")
case .double: ("OptionalDouble", "toOptionalSegmentDouble")
case .boolean: ("Optional<Boolean>", "toOptionalSegmentBoolean")
case .byte: ("Optional<Byte>", "toOptionalSegmentByte")
case .char: ("Optional<Character>", "toOptionalSegmentCharacter")
case .short: ("Optional<Short>", "toOptionalSegmentShort")
case .float: ("Optional<Float>", "toOptionalSegmentFloat")
default:
throw JavaTranslationError.unhandledType(.optional(swiftType))
}
return TranslatedParameter(
javaParameters: [
JavaParameter(name: parameterName, type: JavaType(className: translatedClass))
],
conversion: .call(.placeholder, function: "SwiftRuntime.\(lowerFunc)", withArena: true)
)
}

switch swiftType {
case .nominal(let nominal):
if let knownType = nominal.nominalTypeDecl.knownTypeKind {
switch knownType {
case .data, .dataProtocol:
break
default:
throw JavaTranslationError.unhandledType(.optional(swiftType))
}
}

let translatedTy = try self.translate(swiftType: swiftType)
return TranslatedParameter(
javaParameters: [
JavaParameter(name: parameterName, type: JavaType(className: "Optional<\(translatedTy.description)>"))
],
conversion: .call(.placeholder, function: "SwiftRuntime.toOptionalSegmentInstance", withArena: false)
)
case .existential(let proto), .opaque(let proto):
if
let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind,
let concreteTy = knownTypes.representativeType(of: knownProtocol)
{
return try translateOptionalParameter(
wrappedType: concreteTy,
convention: convention,
parameterName: parameterName,
loweredParam: loweredParam,
methodName: methodName
)
}
throw JavaTranslationError.unhandledType(.optional(swiftType))
case .tuple(let tuple):
if tuple.count == 1 {
return try translateOptionalParameter(wrappedType: tuple[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName)
}
throw JavaTranslationError.unhandledType(.optional(swiftType))
default:
throw JavaTranslationError.unhandledType(.optional(swiftType))
}
}

Expand Down Expand Up @@ -585,7 +663,6 @@ extension FFMSwift2JavaGenerator.TranslatedFunctionSignature {
}
}


extension CType {
/// Map lowered C type to Java type for FFM binding.
var javaType: JavaType {
Expand Down
4 changes: 1 addition & 3 deletions Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,7 @@ extension FFMSwift2JavaGenerator {
// Necessary for native calls and type mapping
"java.lang.foreign.*",
"java.lang.invoke.*",
"java.util.Arrays",
"java.util.stream.Collectors",
"java.util.concurrent.atomic.*",
"java.util.*",
"java.nio.charset.StandardCharsets",
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ extension JNISwift2JavaGenerator {
.unsafeRawPointer, .unsafeMutableRawPointer,
.unsafePointer, .unsafeMutablePointer,
.unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
.unsafeBufferPointer, .unsafeMutableBufferPointer, .data, .dataProtocol:
.unsafeBufferPointer, .unsafeMutableBufferPointer, .optional, .data, .dataProtocol:
nil
}
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ private let swiftSourceFile: SourceFileSyntax = """
public struct UnsafeBufferPointer<Element> {}
public struct UnsafeMutableBufferPointer<Element> {}

public struct Optional<Wrapped> {}

// FIXME: Support 'typealias Void = ()'
public struct Void {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ enum SwiftKnownTypeDeclKind: String, Hashable {
case unsafeMutablePointer = "Swift.UnsafeMutablePointer"
case unsafeBufferPointer = "Swift.UnsafeBufferPointer"
case unsafeMutableBufferPointer = "Swift.UnsafeMutableBufferPointer"
case optional = "Swift.Optional"
case void = "Swift.Void"
case string = "Swift.String"

Expand Down
Loading
Loading