Skip to content

Commit 31fd9e3

Browse files
authored
Merge pull request #303 from rintaro/jextract-ffm-representative
[JExtract/FFM] Translate 'some DataProtocol' parameters to 'Data'
2 parents 8aa06d0 + 36d05c7 commit 31fd9e3

File tree

15 files changed

+277
-56
lines changed

15 files changed

+277
-56
lines changed

Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) {
6363
body(globalBuffer)
6464
}
6565

66+
public func globalReceiveSomeDataProtocol(data: some DataProtocol) -> Int {
67+
p(Array(data).description)
68+
return data.count
69+
}
70+
6671
// ==== Internal helpers
6772

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

Samples/SwiftKitSampleApp/ci-validate.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
set -x
44
set -e
55

6-
./gradlew run
6+
./gradlew run
7+
./gradlew test

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

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

25-
import java.lang.foreign.MemoryLayout;
26-
import java.lang.foreign.MemorySegment;
27-
import java.lang.foreign.ValueLayout;
28-
2925
public class HelloJava2Swift {
3026

3127
public static void main(String[] args) {
@@ -95,6 +91,12 @@ static void examples() {
9591
});
9692
}
9793

94+
try (var arena = AllocatingSwiftArena.ofConfined()) {
95+
var bytes = arena.allocateFrom("hello");
96+
var dat = Data.init(bytes, bytes.byteSize(), arena);
97+
MySwiftLibrary.globalReceiveSomeDataProtocol(dat);
98+
}
99+
98100

99101
System.out.println("DONE.");
100102
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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 static org.junit.jupiter.api.Assertions.*;
21+
22+
public class DataImportTest {
23+
@Test
24+
void test_Data_receiveAndReturn() {
25+
try (var arena = AllocatingSwiftArena.ofConfined()) {
26+
var origBytes = arena.allocateFrom("foobar");
27+
var origDat = Data.init(origBytes, origBytes.byteSize(), arena);
28+
assertEquals(7, origDat.getCount());
29+
30+
var retDat = MySwiftLibrary.globalReceiveReturnData(origDat, arena);
31+
assertEquals(7, retDat.getCount());
32+
retDat.withUnsafeBytes((retBytes) -> {
33+
assertEquals(7, retBytes.byteSize());
34+
var str = retBytes.getString(0);
35+
assertEquals("foobar", str);
36+
});
37+
}
38+
}
39+
40+
@Test
41+
void test_DataProtocol_receive() {
42+
try (var arena = AllocatingSwiftArena.ofConfined()) {
43+
var bytes = arena.allocateFrom("hello");
44+
var dat = Data.init(bytes, bytes.byteSize(), arena);
45+
var result = MySwiftLibrary.globalReceiveSomeDataProtocol(dat);
46+
assertEquals(6, result);
47+
}
48+
}
49+
}

Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ extension CType {
2424
init(cdeclType: SwiftType) throws {
2525
switch cdeclType {
2626
case .nominal(let nominalType):
27-
if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType {
27+
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
2828
if let primitiveCType = knownType.primitiveCType {
2929
self = primitiveCType
3030
return
@@ -68,7 +68,7 @@ extension CType {
6868
case .optional(let wrapped) where wrapped.isPointer:
6969
try self.init(cdeclType: wrapped)
7070

71-
case .metatype, .optional, .tuple:
71+
case .metatype, .optional, .tuple, .opaque, .existential:
7272
throw CDeclToCLoweringError.invalidCDeclType(cdeclType)
7373
}
7474
}
@@ -125,7 +125,7 @@ extension SwiftKnownTypeDeclKind {
125125
.qualified(const: true, volatile: false, type: .void)
126126
)
127127
case .void: .void
128-
case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data:
128+
case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data, .dataProtocol:
129129
nil
130130
}
131131
}

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

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ extension FFMSwift2JavaGenerator {
7777
struct CdeclLowering {
7878
var knownTypes: SwiftKnownTypes
7979

80+
init(knownTypes: SwiftKnownTypes) {
81+
self.knownTypes = knownTypes
82+
}
83+
8084
init(symbolTable: SwiftSymbolTable) {
8185
self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable)
8286
}
@@ -165,7 +169,7 @@ struct CdeclLowering {
165169
)
166170

167171
case .nominal(let nominal):
168-
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
172+
if let knownType = nominal.nominalTypeDecl.knownTypeKind {
169173
if convention == .inout {
170174
// FIXME: Support non-trivial 'inout' for builtin types.
171175
throw LoweringError.inoutNotSupported(type)
@@ -320,6 +324,18 @@ struct CdeclLowering {
320324
conversion: conversion
321325
)
322326

327+
case .opaque(let proto), .existential(let proto):
328+
// If the protocol has a known representative implementation, e.g. `String` for `StringProtocol`
329+
// Translate it as the concrete type.
330+
// NOTE: This is a temporary workaround until we add support for generics.
331+
if
332+
let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind,
333+
let concreteTy = knownTypes.representativeType(of: knownProtocol)
334+
{
335+
return try lowerParameter(concreteTy, convention: convention, parameterName: parameterName)
336+
}
337+
throw LoweringError.unhandledType(type)
338+
323339
case .optional:
324340
throw LoweringError.unhandledType(type)
325341
}
@@ -386,7 +402,7 @@ struct CdeclLowering {
386402

387403
switch type {
388404
case .nominal(let nominal):
389-
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
405+
if let knownType = nominal.nominalTypeDecl.knownTypeKind {
390406
switch knownType {
391407
case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer:
392408
// pointer buffers are lowered to (raw-pointer, count) pair.
@@ -421,7 +437,7 @@ struct CdeclLowering {
421437
// Custom types are not supported yet.
422438
throw LoweringError.unhandledType(type)
423439

424-
case .function, .metatype, .optional, .tuple:
440+
case .function, .metatype, .optional, .tuple, .existential, .opaque:
425441
// TODO: Implement
426442
throw LoweringError.unhandledType(type)
427443
}
@@ -454,7 +470,7 @@ struct CdeclLowering {
454470

455471
case .nominal(let nominal):
456472
// Types from the Swift standard library that we know about.
457-
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
473+
if let knownType = nominal.nominalTypeDecl.knownTypeKind {
458474
switch knownType {
459475
case .unsafePointer, .unsafeMutablePointer:
460476
// Typed pointers are lowered to corresponding raw forms.
@@ -575,7 +591,7 @@ struct CdeclLowering {
575591
conversion: .tupleExplode(conversions, name: outParameterName)
576592
)
577593

578-
case .function(_), .optional(_):
594+
case .function, .optional, .existential, .opaque:
579595
throw LoweringError.unhandledType(type)
580596
}
581597
}

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,16 @@ extension FFMSwift2JavaGenerator {
113113
}
114114

115115
struct JavaTranslation {
116-
var symbolTable: SwiftSymbolTable
116+
var knownTypes: SwiftKnownTypes
117+
118+
init(symbolTable: SwiftSymbolTable) {
119+
self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable)
120+
}
117121

118122
func translate(
119123
_ decl: ImportedFunc
120124
) throws -> TranslatedFunctionDecl {
121-
let lowering = CdeclLowering(symbolTable: symbolTable)
125+
let lowering = CdeclLowering(knownTypes: knownTypes)
122126
let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature)
123127

124128
// Name.
@@ -208,7 +212,7 @@ extension FFMSwift2JavaGenerator {
208212

209213
switch type {
210214
case .nominal(let nominal):
211-
if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType {
215+
if let knownType = nominal.nominalTypeDecl.knownTypeKind {
212216
switch knownType {
213217
case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer:
214218
return TranslatedParameter(
@@ -248,11 +252,12 @@ extension FFMSwift2JavaGenerator {
248252
// 'self'
249253
let selfParameter: TranslatedParameter?
250254
if case .instance(let swiftSelf) = swiftSignature.selfParameter {
251-
selfParameter = try self.translate(
252-
swiftParam: swiftSelf,
255+
selfParameter = try self.translateParameter(
256+
type: swiftSelf.type,
257+
convention: swiftSelf.convention,
258+
parameterName: swiftSelf.parameterName ?? "self",
253259
loweredParam: loweredFunctionSignature.selfParameter!,
254-
methodName: methodName,
255-
parameterName: swiftSelf.parameterName ?? "self"
260+
methodName: methodName
256261
)
257262
} else {
258263
selfParameter = nil
@@ -263,11 +268,12 @@ extension FFMSwift2JavaGenerator {
263268
.map { (idx, swiftParam) in
264269
let loweredParam = loweredFunctionSignature.parameters[idx]
265270
let parameterName = swiftParam.parameterName ?? "_\(idx)"
266-
return try self.translate(
267-
swiftParam: swiftParam,
271+
return try self.translateParameter(
272+
type: swiftParam.type,
273+
convention: swiftParam.convention,
274+
parameterName: parameterName,
268275
loweredParam: loweredParam,
269-
methodName: methodName,
270-
parameterName: parameterName
276+
methodName: methodName
271277
)
272278
}
273279

@@ -285,13 +291,13 @@ extension FFMSwift2JavaGenerator {
285291
}
286292

287293
/// Translate a Swift API parameter to the user-facing Java API parameter.
288-
func translate(
289-
swiftParam: SwiftParameter,
294+
func translateParameter(
295+
type swiftType: SwiftType,
296+
convention: SwiftParameterConvention,
297+
parameterName: String,
290298
loweredParam: LoweredParameter,
291-
methodName: String,
292-
parameterName: String
299+
methodName: String
293300
) throws -> TranslatedParameter {
294-
let swiftType = swiftParam.type
295301

296302
// If there is a 1:1 mapping between this Swift type and a C type, that can
297303
// be expressed as a Java primitive type.
@@ -319,8 +325,8 @@ extension FFMSwift2JavaGenerator {
319325
)
320326

321327
case .nominal(let swiftNominalType):
322-
if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType {
323-
if swiftParam.convention == .inout {
328+
if let knownType = swiftNominalType.nominalTypeDecl.knownTypeKind {
329+
if convention == .inout {
324330
// FIXME: Support non-trivial 'inout' for builtin types.
325331
throw JavaTranslationError.inoutNotSupported(swiftType)
326332
}
@@ -388,6 +394,26 @@ extension FFMSwift2JavaGenerator {
388394
conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true)
389395
)
390396

397+
case .existential(let proto), .opaque(let proto):
398+
// If the protocol has a known representative implementation, e.g. `String` for `StringProtocol`
399+
// Translate it as the concrete type.
400+
// NOTE: This is a temporary workaround until we add support for generics.
401+
if
402+
let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind,
403+
let concreteTy = knownTypes.representativeType(of: knownProtocol)
404+
{
405+
return try translateParameter(
406+
type: concreteTy,
407+
convention: convention,
408+
parameterName: parameterName,
409+
loweredParam: loweredParam,
410+
methodName: methodName
411+
)
412+
}
413+
414+
// Otherwise, not supported yet.
415+
throw JavaTranslationError.unhandledType(swiftType)
416+
391417
case .optional:
392418
throw JavaTranslationError.unhandledType(swiftType)
393419
}
@@ -422,7 +448,7 @@ extension FFMSwift2JavaGenerator {
422448
)
423449

424450
case .nominal(let swiftNominalType):
425-
if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType {
451+
if let knownType = swiftNominalType.nominalTypeDecl.knownTypeKind {
426452
switch knownType {
427453
case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer:
428454
return TranslatedResult(
@@ -476,7 +502,7 @@ extension FFMSwift2JavaGenerator {
476502
// TODO: Implement.
477503
throw JavaTranslationError.unhandledType(swiftType)
478504

479-
case .optional, .function:
505+
case .optional, .function, .existential, .opaque:
480506
throw JavaTranslationError.unhandledType(swiftType)
481507
}
482508

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ extension JNISwift2JavaGenerator {
6666
func translate(swiftType: SwiftType) -> JavaType {
6767
switch swiftType {
6868
case .nominal(let nominalType):
69-
if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType {
69+
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
7070
guard let javaType = translate(knownType: knownType) else {
7171
fatalError("unsupported known type: \(knownType)")
7272
}
@@ -78,7 +78,7 @@ extension JNISwift2JavaGenerator {
7878
case .tuple([]):
7979
return .void
8080

81-
case .metatype, .optional, .tuple, .function:
81+
case .metatype, .optional, .tuple, .function, .existential, .opaque:
8282
fatalError("unsupported type: \(self)")
8383
}
8484
}
@@ -99,10 +99,8 @@ extension JNISwift2JavaGenerator {
9999
.unsafeRawPointer, .unsafeMutableRawPointer,
100100
.unsafePointer, .unsafeMutablePointer,
101101
.unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
102-
.unsafeBufferPointer, .unsafeMutableBufferPointer:
102+
.unsafeBufferPointer, .unsafeMutableBufferPointer, .data, .dataProtocol:
103103
nil
104-
case .data:
105-
fatalError("unimplemented")
106104
}
107105
}
108106
}

Sources/JExtractSwiftLib/Swift2JavaTranslator.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ extension Swift2JavaTranslator {
103103
// If any API uses 'Foundation.Data', import 'Data' as if it's declared in
104104
// this module.
105105
if let dataDecl = self.symbolTable[.data] {
106-
if self.isUsing(dataDecl) {
106+
let dataProtocolDecl = self.symbolTable[.dataProtocol]!
107+
if self.isUsing(where: { $0 == dataDecl || $0 == dataProtocolDecl }) {
107108
visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil)
108109
}
109110
}
@@ -117,12 +118,13 @@ extension Swift2JavaTranslator {
117118
)
118119
}
119120

120-
/// Check if any of the imported decls uses the specified nominal declaration.
121-
func isUsing(_ decl: SwiftNominalTypeDeclaration) -> Bool {
121+
/// Check if any of the imported decls uses a nominal declaration that satisfies
122+
/// the given predicate.
123+
func isUsing(where predicate: (SwiftNominalTypeDeclaration) -> Bool) -> Bool {
122124
func check(_ type: SwiftType) -> Bool {
123125
switch type {
124126
case .nominal(let nominal):
125-
return nominal.nominalTypeDecl == decl
127+
return predicate(nominal.nominalTypeDecl)
126128
case .optional(let ty):
127129
return check(ty)
128130
case .tuple(let tuple):
@@ -131,6 +133,8 @@ extension Swift2JavaTranslator {
131133
return check(fn.resultType) || fn.parameters.contains(where: { check($0.type) })
132134
case .metatype(let ty):
133135
return check(ty)
136+
case .existential(let ty), .opaque(let ty):
137+
return check(ty)
134138
}
135139
}
136140

0 commit comments

Comments
 (0)