Skip to content

Commit 73d7882

Browse files
authored
[JExtract/JNI] Add support for protocols as parameter types (swiftlang#376)
1 parent 5744562 commit 73d7882

36 files changed

+1121
-154
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
public class ConcreteProtocolAB: ProtocolA, ProtocolB {
16+
public let constantA: Int64
17+
public let constantB: Int64
18+
public var mutable: Int64 = 0
19+
20+
public func name() -> String {
21+
return "ConcreteProtocolAB"
22+
}
23+
24+
public init(constantA: Int64, constantB: Int64) {
25+
self.constantA = constantA
26+
self.constantB = constantB
27+
}
28+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
public protocol ProtocolA {
16+
var constantA: Int64 { get }
17+
var mutable: Int64 { get set }
18+
19+
func name() -> String
20+
}
21+
22+
public func takeProtocol(_ proto1: any ProtocolA, _ proto2: some ProtocolA) -> Int64 {
23+
return proto1.constantA + proto2.constantA
24+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
public protocol ProtocolB {
16+
var constantB: Int64 { get }
17+
}
18+
19+
public func takeCombinedProtocol(_ proto: some ProtocolA & ProtocolB) -> Int64 {
20+
return proto.constantA + proto.constantB
21+
}
22+
23+
public func takeGenericProtocol<First: ProtocolA, Second: ProtocolB>(_ proto1: First, _ proto2: Second) -> Int64 {
24+
return proto1.constantA + proto2.constantB
25+
}
26+
27+
public func takeCombinedGenericProtocol<T: ProtocolA & ProtocolB>(_ proto: T) -> Int64 {
28+
return proto.constantA + proto.constantB
29+
}
File renamed without changes.

Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import org.junit.jupiter.api.Test;
1818
import org.swift.swiftkit.core.ConfinedSwiftMemorySession;
19+
import org.swift.swiftkit.core.JNISwiftInstance;
1920
import org.swift.swiftkit.core.SwiftArena;
2021

2122
import java.util.Optional;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 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.core.SwiftArena;
19+
20+
import static org.junit.jupiter.api.Assertions.*;
21+
22+
public class ProtocolTest {
23+
@Test
24+
void takeProtocol() {
25+
try (var arena = SwiftArena.ofConfined()) {
26+
ConcreteProtocolAB proto1 = ConcreteProtocolAB.init(10, 5, arena);
27+
ConcreteProtocolAB proto2 = ConcreteProtocolAB.init(20, 1, arena);
28+
assertEquals(30, MySwiftLibrary.takeProtocol(proto1, proto2));
29+
}
30+
}
31+
32+
@Test
33+
void takeCombinedProtocol() {
34+
try (var arena = SwiftArena.ofConfined()) {
35+
ConcreteProtocolAB proto1 = ConcreteProtocolAB.init(10, 5, arena);
36+
assertEquals(15, MySwiftLibrary.takeCombinedProtocol(proto1));
37+
}
38+
}
39+
40+
@Test
41+
void takeGenericProtocol() {
42+
try (var arena = SwiftArena.ofConfined()) {
43+
ConcreteProtocolAB proto1 = ConcreteProtocolAB.init(10, 5, arena);
44+
ConcreteProtocolAB proto2 = ConcreteProtocolAB.init(20, 1, arena);
45+
assertEquals(11, MySwiftLibrary.takeGenericProtocol(proto1, proto2));
46+
}
47+
}
48+
49+
@Test
50+
void takeCombinedGenericProtocol() {
51+
try (var arena = SwiftArena.ofConfined()) {
52+
ConcreteProtocolAB proto1 = ConcreteProtocolAB.init(10, 5, arena);
53+
assertEquals(15, MySwiftLibrary.takeCombinedGenericProtocol(proto1));
54+
}
55+
}
56+
57+
@Test
58+
void protocolVariables() {
59+
try (var arena = SwiftArena.ofConfined()) {
60+
ProtocolA proto1 = ConcreteProtocolAB.init(10, 5, arena);
61+
assertEquals(10, proto1.getConstantA());
62+
assertEquals(0, proto1.getMutable());
63+
proto1.setMutable(3);
64+
assertEquals(3, proto1.getMutable());
65+
}
66+
}
67+
68+
@Test
69+
void protocolMethod() {
70+
try (var arena = SwiftArena.ofConfined()) {
71+
ProtocolA proto1 = ConcreteProtocolAB.init(10, 5, arena);
72+
assertEquals("ConcreteProtocolAB", proto1.name());
73+
}
74+
}
75+
}

Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,11 @@ extension DeclModifierSyntax {
9595
}
9696

9797
extension WithModifiersSyntax {
98-
var isPublic: Bool {
98+
func isPublic(in type: NominalTypeDeclSyntaxNode?) -> Bool {
99+
if let type, case .protocolDecl(let protocolDecl) = Syntax(type).as(SyntaxEnum.self) {
100+
return protocolDecl.isPublic(in: nil)
101+
}
102+
99103
return self.modifiers.contains { modifier in
100104
modifier.isPublic
101105
}

Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ extension CType {
6868
case .optional(let wrapped) where wrapped.isPointer:
6969
try self.init(cdeclType: wrapped)
7070

71-
case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential:
71+
case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential, .composite:
7272
throw CDeclToCLoweringError.invalidCDeclType(cdeclType)
7373
}
7474
}

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,9 @@ struct CdeclLowering {
344344

345345
case .optional(let wrapped):
346346
return try lowerOptionalParameter(wrapped, convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements)
347+
348+
case .composite:
349+
throw LoweringError.unhandledType(type)
347350
}
348351
}
349352

@@ -412,7 +415,7 @@ struct CdeclLowering {
412415
}
413416
throw LoweringError.unhandledType(.optional(wrappedType))
414417

415-
case .function, .metatype, .optional:
418+
case .function, .metatype, .optional, .composite:
416419
throw LoweringError.unhandledType(.optional(wrappedType))
417420
}
418421
}
@@ -513,7 +516,7 @@ struct CdeclLowering {
513516
// Custom types are not supported yet.
514517
throw LoweringError.unhandledType(type)
515518

516-
case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque:
519+
case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque, .composite:
517520
// TODO: Implement
518521
throw LoweringError.unhandledType(type)
519522
}
@@ -667,7 +670,7 @@ struct CdeclLowering {
667670
conversion: .tupleExplode(conversions, name: outParameterName)
668671
)
669672

670-
case .genericParameter, .function, .optional, .existential, .opaque:
673+
case .genericParameter, .function, .optional, .existential, .opaque, .composite:
671674
throw LoweringError.unhandledType(type)
672675
}
673676
}

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,12 @@ extension FFMSwift2JavaGenerator {
396396

397397
// Indirect return receivers.
398398
for outParameter in translatedSignature.result.outParameters {
399-
let memoryLayout = renderMemoryLayoutValue(for: outParameter.type)
399+
guard case .concrete(let type) = outParameter.type else {
400+
continue
401+
}
402+
let memoryLayout = renderMemoryLayoutValue(for: type)
400403

401-
let arena = if let className = outParameter.type.className,
404+
let arena = if let className = type.className,
402405
analysis.importedTypes[className] != nil {
403406
// Use passed-in 'SwiftArena' for 'SwiftValue'.
404407
"swiftArena$"

0 commit comments

Comments
 (0)