Skip to content
Open
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 @@ -111,5 +111,9 @@ public struct CallbackHolder {
self.callback = callback
}

public init(callback: any CallbackProtocol, _ extra: Int) {
self.callback = callback
}

public var callback: any CallbackProtocol
}
Original file line number Diff line number Diff line change
Expand Up @@ -277,18 +277,14 @@ extension JNISwift2JavaGenerator {
case .opaque(let proto), .existential(let proto):
return try translateProtocolParameter(
protocolType: proto,
methodName: methodName,
parameterName: parameterName,
parentName: parentName
parameterName: parameterName
)

case .genericParameter:
if let concreteTy = type.typeIn(genericParameters: genericParameters, genericRequirements: genericRequirements) {
return try translateProtocolParameter(
protocolType: concreteTy,
methodName: methodName,
parameterName: parameterName,
parentName: parentName
parameterName: parameterName
)
}

Expand Down Expand Up @@ -459,17 +455,13 @@ extension JNISwift2JavaGenerator {

func translateProtocolParameter(
protocolType: SwiftType,
methodName: String,
parameterName: String,
parentName: SwiftQualifiedTypeName?
parameterName: String
) throws -> NativeParameter {
switch protocolType {
case .nominal(let nominalType):
return try translateProtocolParameter(
protocolTypes: [nominalType],
methodName: methodName,
parameterName: parameterName,
parentName: parentName
parameterName: parameterName
)

case .composite(let types):
Expand All @@ -482,9 +474,7 @@ extension JNISwift2JavaGenerator {

return try translateProtocolParameter(
protocolTypes: protocolTypes,
methodName: methodName,
parameterName: parameterName,
parentName: parentName
parameterName: parameterName
)

default:
Expand All @@ -494,9 +484,7 @@ extension JNISwift2JavaGenerator {

private func translateProtocolParameter(
protocolTypes: [SwiftNominalType],
methodName: String,
parameterName: String,
parentName: SwiftQualifiedTypeName?
parameterName: String
) throws -> NativeParameter {
// We allow Java implementations if we are able to generate the needed
// Swift wrappers for all the protocol types.
Expand All @@ -510,10 +498,8 @@ extension JNISwift2JavaGenerator {
],
conversion: .interfaceToSwiftObject(
.placeholder,
swiftWrapperClassName: JNISwift2JavaGenerator.protocolParameterWrapperClassName(
methodName: methodName,
parameterName: parameterName,
parentName: parentName
swiftWrapperClassName: JNISwift2JavaGenerator.protocolParameterBoxClassName(
protocolTypes: protocolTypes
),
protocolTypes: protocolTypes,
allowsJavaImplementations: allowsJavaImplementations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ extension JNISwift2JavaGenerator {
grouping: filteredTypes,
by: { $0.value.sourceFilePath },
) {
emittedProtocolParameterWrappers.removeAll()
logger.warning("Writing types in file group: \(group.key): \(group.value.map(\.key))")

let extractedTypesForThisFile = group.value
Expand Down Expand Up @@ -503,47 +504,49 @@ extension JNISwift2JavaGenerator {
printEscapingClosureSwiftFunctionHelperClasses(&printer, translatedDecl)

// Emit the `@JavaInterface` Swift wrappers any existential / protocol parameters.
printProtocolParameterSwiftFunctionHelperClasses(&printer, decl, translatedDecl)
printProtocolParameterSwiftFunctionHelperClasses(&printer, decl)
}

/// Emits Swift wrapper types for any parameter type that is a protocol
/// (or a `some`/`any`/generic constraint bound to protocols).
private func printProtocolParameterSwiftFunctionHelperClasses(
_ printer: inout SwiftPrinter,
_ decl: ExtractedFunc,
_ translatedDecl: TranslatedFunctionDecl,
) {
let parameters = zip(decl.functionSignature.parameters, translatedDecl.translatedFunctionSignature.parameters)
let protocolParameters = parameters.compactMap { parameter, translatedParameter in
if let concreteType = parameter.type.typeIn(
func protocolParameters(in type: SwiftType) -> [SwiftType] {
if let concreteType = type.typeIn(
genericParameters: decl.functionSignature.genericParameters,
genericRequirements: decl.functionSignature.genericRequirements,
) {
return (translatedParameter.parameter.name, concreteType)
return [concreteType]
}

switch parameter.type {
switch type {
case .opaque(let protocolType),
.existential(let protocolType):
return (translatedParameter.parameter.name, protocolType)

return [protocolType]
case .tuple(let elements):
return elements.flatMap { protocolParameters(in: $0.type) }
default:
return nil
return []
}
}.map { parameterName, protocolType in
}

let protocolParameters = decl.functionSignature.parameters.flatMap { parameter in
protocolParameters(in: parameter.type)
}.map { protocolType in
// We flatten any composite types
switch protocolType {
case .composite(let protocols):
return (parameterName, protocols)
return protocols

default:
return (parameterName, [protocolType])
return [protocolType]
}
}

// For each parameter that is a generic or a protocol,
// we generate a Swift class that conforms to all of those.
for (parameterName, protocolTypes) in protocolParameters {
for protocolTypes in protocolParameters {
let protocolWrappers: [JavaInterfaceSwiftWrapper] = protocolTypes.compactMap { protocolType in
guard let importedType = self.asExtractedNominalTypeDecl(protocolType),
let wrapper = self.interfaceProtocolWrappers[importedType]
Expand All @@ -561,14 +564,13 @@ extension JNISwift2JavaGenerator {
continue
}

let swiftClassName = JNISwift2JavaGenerator.protocolParameterWrapperClassName(
methodName: translatedDecl.name,
parameterName: parameterName,
parentName: translatedDecl.parentName,
let swiftClassName = JNISwift2JavaGenerator.protocolParameterBoxClassName(
protocolTypes: protocolWrappers.map(\.protocolType)
)
guard emittedProtocolParameterWrappers.insert(swiftClassName).inserted else { continue }
let implementingProtocols = protocolWrappers.map(\.wrapperName).joined(separator: .comma)

printer.printBraceBlock("final class \(swiftClassName): \(implementingProtocols)") { printer in
printer.printBraceBlock("fileprivate final class \(swiftClassName): \(implementingProtocols)") { printer in
let variables: [(String, String)] = protocolWrappers.map { wrapper in
(wrapper.javaInterfaceVariableName, wrapper.javaInterfaceName)
}
Expand Down Expand Up @@ -1229,18 +1231,11 @@ extension JNISwift2JavaGenerator {
return newSelfParamName
}

static func protocolParameterWrapperClassName(
methodName: String,
parameterName: String,
parentName: SwiftQualifiedTypeName?,
static func protocolParameterBoxClassName(
protocolTypes: [SwiftNominalType]
) -> String {
let parent =
if let parentName {
"\(parentName.fullFlatName)_"
} else {
""
}
return "_\(parent)\(methodName)_\(parameterName)_Wrapper"
let protocols = protocolTypes.map { $0.nominalTypeDecl.flatName }
return "_Java_\(protocols.joined(separator: "$"))_Box"
}
}

Expand Down
2 changes: 2 additions & 0 deletions Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator {
.map(\.swiftNominal.identity)
)
var interfaceProtocolWrappers: [ExtractedNominalType: JavaInterfaceSwiftWrapper] = [:]
/// Protocol parameter adapter class names already emitted in the current output file.
var emittedProtocolParameterWrappers: Set<String> = []

/// Protocols that should be boxed to support returning them as `any P / some P`
private(set) var existentialProtocolBoxes: [ExtractedNominalType] = []
Expand Down
49 changes: 36 additions & 13 deletions Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ struct JNIProtocolTests {
detectChunkByInitialLines: 1,
expectedChunks: [
"""
final class _SwiftModule_takeGeneric_s_Wrapper: SwiftJavaSomeProtocolWrapper {
fileprivate final class _Java_SomeProtocol_Box: SwiftJavaSomeProtocolWrapper {
let _javaSomeProtocolInterface: JavaSomeProtocol
init(_javaSomeProtocolInterface: JavaSomeProtocol) {
self._javaSomeProtocolInterface = _javaSomeProtocolInterface
Expand Down Expand Up @@ -219,15 +219,15 @@ struct JNIProtocolTests {
xswiftObject$ = xpointer$Existential$
}
else {
xswiftObject$ = _SwiftModule_takeProtocol_x_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: x!, environment: environment))
xswiftObject$ = _Java_SomeProtocol_Box(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: x!, environment: environment))
}
let yswiftObject$: (any SomeProtocol)
if environment.interface.IsInstanceOf(environment, y, _JNIMethodIDCache.JNISwiftInstance.class) != 0 {
...
yswiftObject$ = ypointer$Existential$
}
else {
yswiftObject$ = _SwiftModule_takeProtocol_y_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: y!, environment: environment))
yswiftObject$ = _Java_SomeProtocol_Box(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: y!, environment: environment))
}
SwiftModule.takeProtocol(x: xswiftObject$, y: yswiftObject$)
}
Expand Down Expand Up @@ -267,7 +267,7 @@ struct JNIProtocolTests {
detectChunkByInitialLines: 1,
expectedChunks: [
"""
final class _SwiftModule_takeGeneric_s_Wrapper: SwiftJavaSomeProtocolWrapper {
fileprivate final class _Java_SomeProtocol_Box: SwiftJavaSomeProtocolWrapper {
let _javaSomeProtocolInterface: JavaSomeProtocol
init(_javaSomeProtocolInterface: JavaSomeProtocol) {
self._javaSomeProtocolInterface = _javaSomeProtocolInterface
Expand All @@ -283,7 +283,7 @@ struct JNIProtocolTests {
sswiftObject$ = spointer$Existential$
}
else {
sswiftObject$ = _SwiftModule_takeGeneric_s_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: s!, environment: environment))
sswiftObject$ = _Java_SomeProtocol_Box(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: s!, environment: environment))
}
SwiftModule.takeGeneric(s: sswiftObject$)
}
Expand Down Expand Up @@ -323,7 +323,7 @@ struct JNIProtocolTests {
detectChunkByInitialLines: 1,
expectedChunks: [
"""
final class _SwiftModule_takeComposite_x_Wrapper: SwiftJavaSomeProtocolWrapper, SwiftJavaBWrapper {
fileprivate final class _Java_SomeProtocol$B_Box: SwiftJavaSomeProtocolWrapper, SwiftJavaBWrapper {
let _javaSomeProtocolInterface: JavaSomeProtocol
let _javaBInterface: JavaB
init(_javaSomeProtocolInterface: JavaSomeProtocol, _javaBInterface: JavaB) {
Expand Down Expand Up @@ -357,7 +357,7 @@ struct JNIProtocolTests {
xswiftObject$ = xpointer$Existential$
}
else {
xswiftObject$ = _SwiftModule_takeComposite_x_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: x!, environment: environment), _javaBInterface: JavaB(javaThis: x!, environment: environment))
xswiftObject$ = _Java_SomeProtocol$B_Box(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: x!, environment: environment), _javaBInterface: JavaB(javaThis: x!, environment: environment))
}
SwiftModule.takeComposite(x: xswiftObject$)
}
Expand Down Expand Up @@ -404,7 +404,7 @@ struct JNIProtocolTests {
detectChunkByInitialLines: 1,
expectedChunks: [
"""
final class _Owner_setAnimal_newValue_Wrapper: SwiftJavaAnimalWrapper {
fileprivate final class _Java_Animal_Box: SwiftJavaAnimalWrapper {
...
}
""",
Expand All @@ -413,12 +413,35 @@ struct JNIProtocolTests {
public func Java_com_example_swift_Owner__00024setAnimal__Ljava_lang_Object_2J(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, newValue: jobject?, selfPointer: jlong) {
""",
"""
newValueswiftObject$ = _Owner_setAnimal_newValue_Wrapper(_javaAnimalInterface: JavaAnimal(javaThis: newValue!, environment: environment))
newValueswiftObject$ = _Java_Animal_Box(_javaAnimalInterface: JavaAnimal(javaThis: newValue!, environment: environment))
""",
]
)
}

@Test
func takeAsTuple() throws {
let source = """
public protocol Store {
func value() -> Int64
}
public func take(pair: (any Store, some Store)) {}
"""

try assertOutput(
input: source,
config: config,
.jni,
.swift,
detectChunkByInitialLines: 1,
expectedChunks: [
"fileprivate final class _Java_Store_Box: SwiftJavaStoreWrapper {",
"pair_0swiftObject$ = _Java_Store_Box(_javaStoreInterface: JavaStore(javaThis: pair_0!, environment: environment))",
"pair_1swiftObject$ = _Java_Store_Box(_javaStoreInterface: JavaStore(javaThis: pair_1!, environment: environment))",
]
)
}

@Test
func takeAsOverloadedParameter() throws {
let source = """
Expand All @@ -429,6 +452,7 @@ struct JNIProtocolTests {
public struct Owner {
public func take(first: any Animal) {}
public func take(second: any Animal) {}
public func take(second: any Animal, option: Bool) {}
}
"""

Expand All @@ -439,10 +463,9 @@ struct JNIProtocolTests {
.swift,
detectChunkByInitialLines: 1,
expectedChunks: [
"final class _Owner_takeFirst_first_Wrapper: SwiftJavaAnimalWrapper {",
"firstswiftObject$ = _Owner_takeFirst_first_Wrapper(_javaAnimalInterface: JavaAnimal(javaThis: first!, environment: environment))",
"final class _Owner_takeSecond_second_Wrapper: SwiftJavaAnimalWrapper {",
"secondswiftObject$ = _Owner_takeSecond_second_Wrapper(_javaAnimalInterface: JavaAnimal(javaThis: second!, environment: environment))",
"fileprivate final class _Java_Animal_Box: SwiftJavaAnimalWrapper {",
"firstswiftObject$ = _Java_Animal_Box(_javaAnimalInterface: JavaAnimal(javaThis: first!, environment: environment))",
"secondswiftObject$ = _Java_Animal_Box(_javaAnimalInterface: JavaAnimal(javaThis: second!, environment: environment))",
]
)
}
Expand Down