diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index ce949283..1bcca4fd 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -63,6 +63,11 @@ public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) { body(globalBuffer) } +public func globalReceiveSomeDataProtocol(data: some DataProtocol) -> Int { + p(Array(data).description) + return data.count +} + // ==== Internal helpers func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { diff --git a/Samples/SwiftKitSampleApp/ci-validate.sh b/Samples/SwiftKitSampleApp/ci-validate.sh index 07b42627..c7a68d22 100755 --- a/Samples/SwiftKitSampleApp/ci-validate.sh +++ b/Samples/SwiftKitSampleApp/ci-validate.sh @@ -3,4 +3,5 @@ set -x set -e -./gradlew run \ No newline at end of file +./gradlew run +./gradlew test \ No newline at end of file diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index 985820b1..821b228e 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -22,10 +22,6 @@ import org.swift.swiftkit.ffm.AllocatingSwiftArena; import org.swift.swiftkit.ffm.SwiftRuntime; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; - public class HelloJava2Swift { public static void main(String[] args) { @@ -95,6 +91,12 @@ static void examples() { }); } + try (var arena = AllocatingSwiftArena.ofConfined()) { + var bytes = arena.allocateFrom("hello"); + var dat = Data.init(bytes, bytes.byteSize(), arena); + MySwiftLibrary.globalReceiveSomeDataProtocol(dat); + } + System.out.println("DONE."); } diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java new file mode 100644 index 00000000..52a63f81 --- /dev/null +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// 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 static org.junit.jupiter.api.Assertions.*; + +public class DataImportTest { + @Test + void test_Data_receiveAndReturn() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var origBytes = arena.allocateFrom("foobar"); + var origDat = Data.init(origBytes, origBytes.byteSize(), arena); + assertEquals(7, origDat.getCount()); + + var retDat = MySwiftLibrary.globalReceiveReturnData(origDat, arena); + assertEquals(7, retDat.getCount()); + retDat.withUnsafeBytes((retBytes) -> { + assertEquals(7, retBytes.byteSize()); + var str = retBytes.getString(0); + assertEquals("foobar", str); + }); + } + } + + @Test + void test_DataProtocol_receive() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var bytes = arena.allocateFrom("hello"); + var dat = Data.init(bytes, bytes.byteSize(), arena); + var result = MySwiftLibrary.globalReceiveSomeDataProtocol(dat); + assertEquals(6, result); + } + } +} diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index 9f4a9ac9..4e44e74b 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -24,7 +24,7 @@ extension CType { init(cdeclType: SwiftType) throws { switch cdeclType { case .nominal(let nominalType): - if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { if let primitiveCType = knownType.primitiveCType { self = primitiveCType return @@ -68,7 +68,7 @@ extension CType { case .optional(let wrapped) where wrapped.isPointer: try self.init(cdeclType: wrapped) - case .metatype, .optional, .tuple: + case .metatype, .optional, .tuple, .opaque, .existential: throw CDeclToCLoweringError.invalidCDeclType(cdeclType) } } @@ -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: + case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data, .dataProtocol: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 488bd4e7..0c6c707c 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -77,6 +77,10 @@ extension FFMSwift2JavaGenerator { struct CdeclLowering { var knownTypes: SwiftKnownTypes + init(knownTypes: SwiftKnownTypes) { + self.knownTypes = knownTypes + } + init(symbolTable: SwiftSymbolTable) { self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) } @@ -165,7 +169,7 @@ struct CdeclLowering { ) case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { if convention == .inout { // FIXME: Support non-trivial 'inout' for builtin types. throw LoweringError.inoutNotSupported(type) @@ -320,6 +324,18 @@ struct CdeclLowering { conversion: conversion ) + case .opaque(let proto), .existential(let proto): + // If the protocol has a known representative implementation, e.g. `String` for `StringProtocol` + // Translate it as the concrete type. + // NOTE: This is a temporary workaround until we add support for generics. + if + let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, + let concreteTy = knownTypes.representativeType(of: knownProtocol) + { + return try lowerParameter(concreteTy, convention: convention, parameterName: parameterName) + } + throw LoweringError.unhandledType(type) + case .optional: throw LoweringError.unhandledType(type) } @@ -386,7 +402,7 @@ struct CdeclLowering { switch type { case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: // pointer buffers are lowered to (raw-pointer, count) pair. @@ -421,7 +437,7 @@ struct CdeclLowering { // Custom types are not supported yet. throw LoweringError.unhandledType(type) - case .function, .metatype, .optional, .tuple: + case .function, .metatype, .optional, .tuple, .existential, .opaque: // TODO: Implement throw LoweringError.unhandledType(type) } @@ -454,7 +470,7 @@ struct CdeclLowering { case .nominal(let nominal): // Types from the Swift standard library that we know about. - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { case .unsafePointer, .unsafeMutablePointer: // Typed pointers are lowered to corresponding raw forms. @@ -575,7 +591,7 @@ struct CdeclLowering { conversion: .tupleExplode(conversions, name: outParameterName) ) - case .function(_), .optional(_): + case .function, .optional, .existential, .opaque: throw LoweringError.unhandledType(type) } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index fe1806a9..db706a91 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -113,12 +113,16 @@ extension FFMSwift2JavaGenerator { } struct JavaTranslation { - var symbolTable: SwiftSymbolTable + var knownTypes: SwiftKnownTypes + + init(symbolTable: SwiftSymbolTable) { + self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) + } func translate( _ decl: ImportedFunc ) throws -> TranslatedFunctionDecl { - let lowering = CdeclLowering(symbolTable: symbolTable) + let lowering = CdeclLowering(knownTypes: knownTypes) let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. @@ -208,7 +212,7 @@ extension FFMSwift2JavaGenerator { switch type { case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedParameter( @@ -248,11 +252,12 @@ extension FFMSwift2JavaGenerator { // 'self' let selfParameter: TranslatedParameter? if case .instance(let swiftSelf) = swiftSignature.selfParameter { - selfParameter = try self.translate( - swiftParam: swiftSelf, + selfParameter = try self.translateParameter( + type: swiftSelf.type, + convention: swiftSelf.convention, + parameterName: swiftSelf.parameterName ?? "self", loweredParam: loweredFunctionSignature.selfParameter!, - methodName: methodName, - parameterName: swiftSelf.parameterName ?? "self" + methodName: methodName ) } else { selfParameter = nil @@ -263,11 +268,12 @@ extension FFMSwift2JavaGenerator { .map { (idx, swiftParam) in let loweredParam = loweredFunctionSignature.parameters[idx] let parameterName = swiftParam.parameterName ?? "_\(idx)" - return try self.translate( - swiftParam: swiftParam, + return try self.translateParameter( + type: swiftParam.type, + convention: swiftParam.convention, + parameterName: parameterName, loweredParam: loweredParam, - methodName: methodName, - parameterName: parameterName + methodName: methodName ) } @@ -285,13 +291,13 @@ extension FFMSwift2JavaGenerator { } /// Translate a Swift API parameter to the user-facing Java API parameter. - func translate( - swiftParam: SwiftParameter, + func translateParameter( + type swiftType: SwiftType, + convention: SwiftParameterConvention, + parameterName: String, loweredParam: LoweredParameter, - methodName: String, - parameterName: String + methodName: String ) throws -> TranslatedParameter { - let swiftType = swiftParam.type // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. @@ -319,8 +325,8 @@ extension FFMSwift2JavaGenerator { ) case .nominal(let swiftNominalType): - if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { - if swiftParam.convention == .inout { + if let knownType = swiftNominalType.nominalTypeDecl.knownTypeKind { + if convention == .inout { // FIXME: Support non-trivial 'inout' for builtin types. throw JavaTranslationError.inoutNotSupported(swiftType) } @@ -388,6 +394,26 @@ extension FFMSwift2JavaGenerator { conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) ) + case .existential(let proto), .opaque(let proto): + // If the protocol has a known representative implementation, e.g. `String` for `StringProtocol` + // Translate it as the concrete type. + // NOTE: This is a temporary workaround until we add support for generics. + if + let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, + let concreteTy = knownTypes.representativeType(of: knownProtocol) + { + return try translateParameter( + type: concreteTy, + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName + ) + } + + // Otherwise, not supported yet. + throw JavaTranslationError.unhandledType(swiftType) + case .optional: throw JavaTranslationError.unhandledType(swiftType) } @@ -422,7 +448,7 @@ extension FFMSwift2JavaGenerator { ) case .nominal(let swiftNominalType): - if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { + if let knownType = swiftNominalType.nominalTypeDecl.knownTypeKind { switch knownType { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedResult( @@ -476,7 +502,7 @@ extension FFMSwift2JavaGenerator { // TODO: Implement. throw JavaTranslationError.unhandledType(swiftType) - case .optional, .function: + case .optional, .function, .existential, .opaque: throw JavaTranslationError.unhandledType(swiftType) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 929a332c..f006b4a8 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -66,7 +66,7 @@ extension JNISwift2JavaGenerator { func translate(swiftType: SwiftType) -> JavaType { switch swiftType { case .nominal(let nominalType): - if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = translate(knownType: knownType) else { fatalError("unsupported known type: \(knownType)") } @@ -78,7 +78,7 @@ extension JNISwift2JavaGenerator { case .tuple([]): return .void - case .metatype, .optional, .tuple, .function: + case .metatype, .optional, .tuple, .function, .existential, .opaque: fatalError("unsupported type: \(self)") } } @@ -99,10 +99,8 @@ extension JNISwift2JavaGenerator { .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer: + .unsafeBufferPointer, .unsafeMutableBufferPointer, .data, .dataProtocol: nil - case .data: - fatalError("unimplemented") } } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 82798c15..07023b09 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -103,7 +103,8 @@ extension Swift2JavaTranslator { // If any API uses 'Foundation.Data', import 'Data' as if it's declared in // this module. if let dataDecl = self.symbolTable[.data] { - if self.isUsing(dataDecl) { + let dataProtocolDecl = self.symbolTable[.dataProtocol]! + if self.isUsing(where: { $0 == dataDecl || $0 == dataProtocolDecl }) { visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil) } } @@ -117,12 +118,13 @@ extension Swift2JavaTranslator { ) } - /// Check if any of the imported decls uses the specified nominal declaration. - func isUsing(_ decl: SwiftNominalTypeDeclaration) -> Bool { + /// Check if any of the imported decls uses a nominal declaration that satisfies + /// the given predicate. + func isUsing(where predicate: (SwiftNominalTypeDeclaration) -> Bool) -> Bool { func check(_ type: SwiftType) -> Bool { switch type { case .nominal(let nominal): - return nominal.nominalTypeDecl == decl + return predicate(nominal.nominalTypeDecl) case .optional(let ty): return check(ty) case .tuple(let tuple): @@ -131,6 +133,8 @@ extension Swift2JavaTranslator { return check(fn.resultType) || fn.parameters.contains(where: { check($0.type) }) case .metatype(let ty): return check(ty) + case .existential(let ty), .opaque(let ty): + return check(ty) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 21862c16..47abb7f0 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -86,7 +86,9 @@ private let swiftSourceFile: SourceFileSyntax = """ """ private let foundationSourceFile: SourceFileSyntax = """ - public struct Data { + public protocol DataProtocol {} + + public struct Data: DataProtocol { public init(bytes: UnsafeRawPointer, count: Int) public var count: Int { get } public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index 0ac915c7..d6af5920 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -39,6 +39,7 @@ enum SwiftKnownTypeDeclKind: String, Hashable { case void = "Swift.Void" case string = "Swift.String" + case dataProtocol = "Foundation.DataProtocol" case data = "Foundation.Data" var moduleAndName: (module: String, name: String) { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift index ea4371a3..8c70f7e2 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift @@ -35,6 +35,9 @@ struct SwiftKnownTypes { var unsafeRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawPointer])) } var unsafeMutableRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeMutableRawPointer])) } + var dataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.dataProtocol])) } + var data: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.data])) } + func unsafePointer(_ pointeeType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( @@ -70,4 +73,13 @@ struct SwiftKnownTypes { ) ) } + + /// Returns the known representative concrete type if there is one for the + /// given protocol kind. E.g. `String` for `StringProtocol` + func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftType? { + switch knownProtocol { + case .dataProtocol: return self.data + default: return nil + } + } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index b63f930c..8be645c8 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -52,7 +52,7 @@ package class SwiftNominalTypeDeclaration { /// Identify this nominal declaration as one of the known standard library /// types, like 'Swift.Int[. - lazy var knownStandardLibraryType: SwiftKnownTypeDeclKind? = { + lazy var knownTypeKind: SwiftKnownTypeDeclKind? = { self.computeKnownStandardLibraryType() }() diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index c2ddd2a5..531ab45c 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -16,12 +16,25 @@ import SwiftSyntax /// Describes a type in the Swift type system. enum SwiftType: Equatable { + case nominal(SwiftNominalType) + indirect case function(SwiftFunctionType) + + /// `.Type` indirect case metatype(SwiftType) - case nominal(SwiftNominalType) + + /// `?` indirect case optional(SwiftType) + + /// `(, )` case tuple([SwiftType]) + /// `any ` + indirect case existential(SwiftType) + + /// `some ` + indirect case opaque(SwiftType) + static var void: Self { return .tuple([]) } @@ -30,7 +43,7 @@ enum SwiftType: Equatable { switch self { case .nominal(let nominal): nominal case .tuple(let elements): elements.count == 1 ? elements[0].asNominalType : nil - case .function, .metatype, .optional: nil + case .function, .metatype, .optional, .existential, .opaque: nil } } @@ -54,7 +67,7 @@ enum SwiftType: Equatable { var isPointer: Bool { switch self { case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { return knownType.isPointer } default: @@ -73,7 +86,7 @@ enum SwiftType: Equatable { return nominal.nominalTypeDecl.isReferenceType case .metatype, .function: return true - case .optional, .tuple: + case .optional, .tuple, .existential, .opaque: return false } } @@ -84,7 +97,7 @@ extension SwiftType: CustomStringConvertible { /// requires parentheses. private var postfixRequiresParentheses: Bool { switch self { - case .function: true + case .function, .existential, .opaque: true case .metatype, .nominal, .optional, .tuple: false } } @@ -103,6 +116,10 @@ extension SwiftType: CustomStringConvertible { return "\(wrappedType.description)?" case .tuple(let elements): return "(\(elements.map(\.description).joined(separator: ", ")))" + case .existential(let constraintType): + return "any \(constraintType)" + case .opaque(let constraintType): + return "some \(constraintType)" } } } @@ -159,8 +176,7 @@ extension SwiftType { switch type.as(TypeSyntaxEnum.self) { case .arrayType, .classRestrictionType, .compositionType, .dictionaryType, .missingType, .namedOpaqueReturnType, - .packElementType, .packExpansionType, .someOrAnyType, - .suppressedType: + .packElementType, .packExpansionType, .suppressedType: throw TypeTranslationError.unimplementedType(type) case .attributedType(let attributedType): @@ -254,6 +270,13 @@ extension SwiftType { self = try .tuple(tupleType.elements.map { element in try SwiftType(element.type, symbolTable: symbolTable) }) + + case .someOrAnyType(let someOrAntType): + if someOrAntType.someOrAnySpecifier.tokenKind == .keyword(.some) { + self = .opaque(try SwiftType(someOrAntType.constraint, symbolTable: symbolTable)) + } else { + self = .opaque(try SwiftType(someOrAntType.constraint, symbolTable: symbolTable)) + } } } diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index 75613407..104f7f0e 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -16,7 +16,7 @@ import JExtractSwiftLib import Testing final class DataImportTests { - let class_interfaceFile = + let data_interfaceFile = """ import Foundation @@ -24,11 +24,19 @@ final class DataImportTests { public func returnData() -> Data """ + let dataProtocol_interfaceFile = + """ + import Foundation + + public func receiveDataProtocol(dat: some DataProtocol) + """ + + @Test("Import Data: Swift thunks") - func swiftThunk() throws { + func data_swiftThunk() throws { try assertOutput( - input: class_interfaceFile, .ffm, .swift, + input: data_interfaceFile, .ffm, .swift, expectedChunks: [ """ import Foundation @@ -80,10 +88,10 @@ final class DataImportTests { } @Test("Import Data: JavaBindings") - func javaBindings() throws { + func data_javaBindings() throws { try assertOutput( - input: class_interfaceFile, .ffm, .java, + input: data_interfaceFile, .ffm, .java, expectedChunks: [ """ /** @@ -325,4 +333,78 @@ final class DataImportTests { ) } + @Test("Import DataProtocol: Swift thunks") + func dataProtocol_swiftThunk() throws { + try assertOutput( + input: dataProtocol_interfaceFile, .ffm, .swift, + expectedChunks: [ + """ + import Foundation + """, + """ + @_cdecl("swiftjava_SwiftModule_receiveDataProtocol_dat") + public func swiftjava_SwiftModule_receiveDataProtocol_dat(_ dat: UnsafeRawPointer) { + receiveDataProtocol(dat: dat.assumingMemoryBound(to: Data.self).pointee) + } + """, + + // Just to make sure 'Data' is imported. + """ + @_cdecl("swiftjava_getType_SwiftModule_Data") + """ + ] + ) + } + + @Test("Import DataProtocol: JavaBindings") + func dataProtocol_javaBindings() throws { + + try assertOutput( + input: dataProtocol_interfaceFile, .ffm, .java, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_receiveDataProtocol_dat(const void *dat) + * } + */ + private static class swiftjava_SwiftModule_receiveDataProtocol_dat { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* dat: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveDataProtocol_dat"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment dat) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(dat); + } + HANDLE.invokeExact(dat); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func receiveDataProtocol(dat: some DataProtocol) + * } + */ + public static void receiveDataProtocol(Data dat) { + swiftjava_SwiftModule_receiveDataProtocol_dat.call(dat.$memorySegment()); + } + """, + + // Just to make sure 'Data' is imported. + """ + public final class Data + """ + ] + ) + } }