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 @@ -389,6 +389,7 @@ extension JNISwift2JavaGenerator {
/// function (including requirements inherited from refined protocols).
private func printExistentialBoxDispatchThunks(_ printer: inout SwiftPrinter, _ type: ExtractedNominalType) {
let boxParentName = SwiftQualifiedTypeName(type.swiftNominal.javaExistentialBoxName)
var emittedCDeclSymbols: Set<String> = []

for method in self.allProtocolRequirementMethods(of: type) {
guard var translated = try? self.javaTranslator.translate(method) else {
Expand All @@ -397,6 +398,14 @@ extension JNISwift2JavaGenerator {
}
translated.parentName = boxParentName

let cName = cDeclSymbolName(for: translated)
// A protocol requirement and its default implementation can be represented
// by distinct extracted declarations while mapping to the same JNI symbol.
// The existential box only needs one dispatch thunk for that symbol.
guard emittedCDeclSymbols.insert(cName).inserted else {
continue
}

printCDecl(&printer, translated) { printer in
self.printFunctionDowncall(&printer, method)
}
Expand Down Expand Up @@ -838,12 +847,15 @@ extension JNISwift2JavaGenerator {
)
}

private func printCDecl(
_ printer: inout SwiftPrinter,
_ translatedDecl: TranslatedFunctionDecl,
_ body: (inout SwiftPrinter) -> Void,
) {
let nativeSignature = translatedDecl.nativeFunctionSignature
private func cDeclSymbolName(for translatedDecl: TranslatedFunctionDecl) -> String {
cDeclSymbolName(
javaMethodName: translatedDecl.nativeFunctionName,
parentName: translatedDecl.parentName,
parameters: nativeParameters(for: translatedDecl.nativeFunctionSignature)
)
}

private func nativeParameters(for nativeSignature: NativeFunctionSignature) -> [JavaParameter] {
var parameters = nativeSignature.parameters.flatMap(\.parameters)

if let selfParameter = nativeSignature.selfParameter {
Expand All @@ -854,6 +866,17 @@ extension JNISwift2JavaGenerator {
}
parameters += nativeSignature.result.outParameters

return parameters
}

private func printCDecl(
_ printer: inout SwiftPrinter,
_ translatedDecl: TranslatedFunctionDecl,
_ body: (inout SwiftPrinter) -> Void,
) {
let nativeSignature = translatedDecl.nativeFunctionSignature
let parameters = nativeParameters(for: nativeSignature)

printCDecl(
&printer,
javaMethodName: translatedDecl.nativeFunctionName,
Expand All @@ -873,15 +896,10 @@ extension JNISwift2JavaGenerator {
resultType: JavaType,
_ body: (inout SwiftPrinter) -> Void,
) {
let jniSignature = parameters.reduce(into: "") { signature, parameter in
signature += parameter.type.jniTypeSignature
}

let cName = String.jniSymbolName(
package: self.javaPackage,
parent: parentName,
method: javaMethodName,
signature: jniSignature,
let cName = cDeclSymbolName(
javaMethodName: javaMethodName,
parentName: parentName,
parameters: parameters
)

self.generatedCDeclSymbolNames.append(cName)
Expand Down Expand Up @@ -914,6 +932,23 @@ extension JNISwift2JavaGenerator {
}
}

private func cDeclSymbolName(
javaMethodName: String,
parentName: SwiftQualifiedTypeName,
parameters: [JavaParameter],
) -> String {
let jniSignature = parameters.reduce(into: "") { signature, parameter in
signature += parameter.type.jniTypeSignature
}

return String.jniSymbolName(
package: self.javaPackage,
parent: parentName,
method: javaMethodName,
signature: jniSignature,
)
}

private func printJNICache(_ printer: inout SwiftPrinter, _ type: ExtractedNominalType) {
let cacheName = JNICaching.cacheName(for: type)
let jniClassName = "\(javaPackagePath)/\(type.effectiveJavaTypeName.jniEscapedName)"
Expand Down
12 changes: 12 additions & 0 deletions Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func assertOutput(
moduleJavaPackages: [String: String] = [:],
expectedChunks: [String],
notExpectedChunks: [String] = [],
expectedOccurrences: [String: Int] = [:],
fileID: String = #fileID,
filePath: String = #filePath,
line: Int = #line,
Expand Down Expand Up @@ -112,6 +113,7 @@ func assertOutput(
dump: dump,
expectedChunks: expectedChunks,
notExpectedChunks: notExpectedChunks,
expectedOccurrences: expectedOccurrences,
detectChunkByInitialLines: _detectChunkByInitialLines,
fileID: fileID,
filePath: filePath,
Expand All @@ -126,6 +128,7 @@ func assertOutput(
dump: Bool = false,
expectedChunks: [String],
notExpectedChunks: [String] = [],
expectedOccurrences: [String: Int] = [:],
detectChunkByInitialLines _detectChunkByInitialLines: Int = 4,
fileID: String = #fileID,
filePath: String = #filePath,
Expand All @@ -149,6 +152,15 @@ func assertOutput(
)
}

for (expectedChunk, expectedCount) in expectedOccurrences {
let actualCount = output.components(separatedBy: expectedChunk).count - 1
#expect(
actualCount == expectedCount,
"Expected '\(expectedChunk)' to occur \(expectedCount) time(s), but found \(actualCount).",
sourceLocation: sourceLocation
)
}

let gotLines = output.split(separator: "\n").filter { l in
l.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count > 0
}
Expand Down
37 changes: 37 additions & 0 deletions Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ struct JNIProtocolTests {
public func makeChild() -> any ChildProtocol
"""

let protocolDefaultImplementationSource = """
public protocol Test {
public func action()
}

public extension Test {
public func action() {}
}

public func makeTest() -> any Test
"""

@Test
func generatesJavaInterface() throws {
try assertOutput(
Expand Down Expand Up @@ -542,6 +554,31 @@ struct JNIProtocolTests {
)
}

@Test
func existentialBoxDispatchThunkWithDefaultImplementationIsUnique() throws {
var config = config
config.enableJavaCallbacks = false

try assertOutput(
input: protocolDefaultImplementationSource,
config: config,
.jni,
.swift,
detectChunkByInitialLines: 1,
expectedChunks: [
"""
@_cdecl("Java_com_example_swift_TestBox__00024action_1__JJ")
public func Java_com_example_swift_TestBox__00024action_1__JJ(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, selfPointer: jlong, selfTypePointer: jlong) {
...
}
"""
],
expectedOccurrences: [
"@_cdecl(\"Java_com_example_swift_TestBox__00024action_1__JJ\")": 1
]
)
}

@Test
func existentialBoxPropertyAccessors_swift() throws {
try assertOutput(
Expand Down
Loading