diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift index 349062ef1..be8f7445c 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift @@ -31,3 +31,9 @@ public enum EnumWithCaseNameValue { public var message: String } } + +public enum ComplexAssociatedValues { + case generic(MyID, GenericEnum) + case optionalTypealiasedGeneric(id: MyIntID?) + case array([String]) +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift index 0968489eb..ff4d579ed 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift @@ -22,6 +22,8 @@ public struct MyID { } } +public typealias MyIntID = MyID + public enum MyIDs { public static func makeIntID(_ value: Int) -> MyID { MyID(value) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift index 728d7ce01..ff5bce585 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift @@ -24,6 +24,10 @@ public func labeledTuple() -> (x: Int32, y: Int32) { (x: 10, y: 20) } +public func echoSingleTuple(input: (String)) -> (String) { + input +} + public func echoTriple(triple: (Bool, Double, Int64)) -> (Bool, Double, Int64) { triple } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java index e9e01891a..275963981 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java @@ -19,6 +19,9 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.List; +import java.util.Optional; + public class EnumTest { @Test void enumWithValueCases() { @@ -42,11 +45,54 @@ void enumWithCaseNameValue() { try (var arena = SwiftArena.ofConfined()) { var success = EnumWithCaseNameValue.Success.init("ok", arena); EnumWithCaseNameValue e = EnumWithCaseNameValue.success(success, arena); - + switch (e.getCase(arena)) { case EnumWithCaseNameValue.Case.Success(var s): assertEquals("ok", s.getMessage()); } } } + + @Test + void complexAssociatedValues_generic() { + try (var arena = SwiftArena.ofConfined()) { + var e = ComplexAssociatedValues.generic( + MyIDs.makeIntID(42L, arena), + MySwiftLibrary.makeIntGenericEnum(arena), + arena + ); + assertEquals( + Optional.of("42"), + e.getAsGeneric(arena).map(v -> v.arg0().getDescription()) + ); + } + } + + @Test + void complexAssociatedValues_optionalTypealiasedGeneric() { + try (var arena = SwiftArena.ofConfined()) { + var e = ComplexAssociatedValues.optionalTypealiasedGeneric( + Optional.of(MyIDs.makeIntID(42L, arena)), + arena + ); + assertDoesNotThrow(() -> { + var id = e.getAsOptionalTypealiasedGeneric(arena).orElseThrow().id(); + assertEquals(Optional.of("42"), id.map(MyID::getDescription)); + }); + } + } + + @Test + void complexAssociatedValues_array() { + try (var arena = SwiftArena.ofConfined()) { + var e = ComplexAssociatedValues.array( + new String[]{"Hello", "World"}, + arena + ); + assertDoesNotThrow(() -> { + var value = e.getAsArray().orElseThrow().arg0(); + assertArrayEquals(new String[]{"Hello", "World"}, value); + }); + } + } } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java index 3eb36209f..b58e12ab8 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java @@ -56,6 +56,13 @@ void labeledTuple() { Tuple2 check = result; } + @Test + void echoSingleTuple() { + var input = "Swift"; + String result = MySwiftLibrary.echoSingleTuple(input); + assertEquals(input, result); + } + @Test void echoTriple() { Tuple3 input = new Tuple3<>(true, 3.14, 100L); diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 21d303d31..3455cc2fe 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -503,22 +503,12 @@ extension JNISwift2JavaGenerator { continue } - let members = translatedCase.translatedValues.map { - $0.parameter.renderParameter() + let members = translatedCase.parameters.map { + $0.renderParameter() } - let caseName = enumCase.name.firstCharacterUppercased // Print record - printer.printBraceBlock("record \(caseName)(\(members.joined(separator: ", "))) implements Case") { - printer in - let nativeParameters = zip(translatedCase.translatedValues, translatedCase.parameterConversions).map { - value, - conversion in - "\(conversion.native.javaType) \(value.parameter.name)" - } - - printer.print("record _NativeParameters(\(nativeParameters.joined(separator: ", "))) {}") - } + printer.print("record \(translatedCase.name)(\(members.joined(separator: ", "))) implements Case {}") } } printer.println() @@ -533,10 +523,16 @@ extension JNISwift2JavaGenerator { guard let translatedCase = self.translatedEnumCase(for: enumCase) else { continue } - let arenaArgument = translatedCase.requiresSwiftArena ? "swiftArena" : "" - printer.print( - "case \(enumCase.name.uppercased()) -> this.getAs\(enumCase.name.firstCharacterUppercased)(\(arenaArgument)).orElseThrow();" - ) + if enumCase.parameters.isEmpty { + printer.print( + "case \(enumCase.name.uppercased()) -> new Case.\(translatedCase.name)();" + ) + } else { + let arenaArgument = translatedCase.requiresSwiftArena ? "swiftArena" : "" + printer.print( + "case \(enumCase.name.uppercased()) -> this.getAs\(translatedCase.name)(\(arenaArgument)).orElseThrow();" + ) + } } } } @@ -557,10 +553,49 @@ extension JNISwift2JavaGenerator { private func printEnumCases(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { for enumCase in decl.cases { guard let translatedCase = self.translatedEnumCase(for: enumCase) else { - return + continue + } + + let caseType = JavaType.class(package: nil, name: "Case.\(translatedCase.name)") + let resultType = JavaType.optional(caseType) + if let getAsCaseFunctionDecl = translatedCase.getAsCaseFunction, + var getAsCaseFunction = self.translatedDecl(for: getAsCaseFunctionDecl) + { + let args = + if enumCase.parameters.count == 1 { + "t" + } else { + (0.. TranslatedEnumCase { - let nativeTranslation = NativeJavaTranslation( - config: self.config, - javaPackage: self.javaPackage, - javaClassLookupTable: self.javaClassLookupTable, - knownTypes: self.knownTypes, - protocolWrappers: self.protocolWrappers, - logger: self.logger, - ) - let methodName = "" // TODO: Used for closures, replace with better name? - let parentName = SwiftQualifiedTypeName("") // TODO: Used for closures, replace with better name? - let translatedValues = try self.translateParameters( - enumCase.parameters.map { ($0.name, $0.type) }, - methodName: methodName, - parentName: parentName, - genericParameters: [], - genericRequirements: [], - ) - - let conversions = try enumCase.parameters.enumerated().map { idx, parameter in + let parameterResults = try enumCase.parameters.enumerated().map { idx, parameter in let resultName = parameter.name ?? "arg\(idx)" - var translatedResult = try self.translateResult( + let translatedResult = try self.translateResult( swiftType: parameter.type, methodName: methodName, resultName: resultName ) - translatedResult.conversion = .replacingPlaceholder( - translatedResult.conversion, - placeholder: "$nativeParameters.\(resultName)", - ) - let nativeResult = try nativeTranslation.translateResult( - swiftType: parameter.type, - methodName: methodName, - resultName: resultName + return ( + parameter: JavaParameter(name: resultName, type: translatedResult.javaType), + requiresSwiftArena: translatedResult.conversion.requiresSwiftArena ) - return (translated: translatedResult, native: nativeResult) } - let caseName = enumCase.name.firstCharacterUppercased - let enumName = enumCase.enumType.nominalTypeDecl.name - let caseType = JavaType.class(package: nil, name: "Case.\(caseName)") - let nativeParametersType = JavaType.class(package: nil, name: "Case.\(caseName)._NativeParameters") - let getAsCaseName = "getAs\(caseName)" - // If the case has no parameters, we can skip the native call. - let constructRecordConversion = JavaNativeConversionStep.method( - .constant("Optional"), - function: "of", - arguments: [ - .constructJavaClass( - .commaSeparated(conversions.map(\.translated.conversion)), - caseType, - ) - ], - ) - var exceptions: [JavaExceptionType] = [] - - if enumCase.parameters.contains(where: \.type.isArchDependingInteger) { - exceptions.append(.integerOverflow) - } - - let isGenericParent = enumCase.caseFunction.parentType?.asNominalTypeDeclaration?.isGeneric == true - - let getAsCaseFunction = TranslatedFunctionDecl( - name: getAsCaseName, - isStatic: false, - isThrowing: false, - isAsync: false, - nativeFunctionName: "$\(getAsCaseName)", - parentName: SwiftQualifiedTypeName(enumName), - functionTypes: [], - translatedFunctionSignature: TranslatedFunctionSignature( - selfParameter: TranslatedParameter( - parameter: JavaParameter(name: "selfPointer", type: .long), - conversion: .aggregate( - [ - .ifStatement( - .constant("getDiscriminator() != Discriminator.\(caseName.uppercased())"), - thenExp: .constant("return Optional.empty();"), - ), - .valueMemoryAddress(.placeholder), - ] - ), - ), - selfTypeParameter: !isGenericParent - ? nil - : .init( - parameter: JavaParameter(name: "selfTypePointer", type: .long), - conversion: .typeMetadataAddress(.placeholder), - ), - parameters: [], - result: TranslatedResult( - javaType: .optional(caseType), - nativeJavaType: nativeParametersType, - outParameters: conversions.flatMap(\.translated.outParameters), - conversion: enumCase.parameters.isEmpty - ? constructRecordConversion - : .aggregate(variable: ("$nativeParameters", nativeParametersType), [constructRecordConversion]), - ), - exceptions: exceptions, - ), - nativeFunctionSignature: NativeFunctionSignature( - selfParameter: NativeParameter( - parameters: [JavaParameter(name: "selfPointer", type: .long)], - conversion: .extractSwiftValue(.placeholder, swiftType: .nominal(enumCase.enumType), allowNil: false), - indirectConversion: nil, - conversionCheck: nil, - ), - selfTypeParameter: !isGenericParent - ? nil - : .init( - parameters: [JavaParameter(name: "selfTypePointer", type: .long)], - conversion: .extractMetatypeValue(.placeholder), - indirectConversion: nil, - conversionCheck: nil, - ), - parameters: [], - result: NativeResult( - javaType: nativeParametersType, - conversion: .placeholder, - outParameters: conversions.flatMap(\.native.outParameters), - ), - ), + let associatedValueTypes = enumCase.parameters.map { param in + param.type.description + }.joined(separator: ", ") + let javaCaseClassName = enumCase.name.firstCharacterUppercased + let resultType = knownTypes.optionalSugar( + .tuple( + enumCase.parameters.map { + SwiftTupleElement(label: nil, type: $0.type) + } + ) ) + let getAsCaseFunction: ImportedFunc? = + if !enumCase.parameters.isEmpty { + ImportedFunc( + module: enumCase.enumType.nominalTypeDecl.moduleName, + swiftDecl: DeclSyntax("func getAs\(raw: javaCaseClassName)() -> (\(raw: associatedValueTypes))?"), + name: "getAs\(javaCaseClassName)", + apiKind: .function, + functionSignature: .init( + selfParameter: .instance(convention: .byValue, swiftType: .nominal(enumCase.enumType)), + parameters: [], + result: .init(convention: .direct, type: resultType), + effectSpecifiers: [], + genericParameters: [], + genericRequirements: [] + ) + ) + } else { + nil + } return TranslatedEnumCase( - name: enumCase.name.firstCharacterUppercased, - enumName: enumCase.enumType.nominalTypeDecl.name, + name: javaCaseClassName, original: enumCase, - translatedValues: translatedValues, - parameterConversions: conversions, + parameters: parameterResults.map(\.parameter), getAsCaseFunction: getAsCaseFunction, + requiresSwiftArena: parameterResults.contains(where: \.requiresSwiftArena) ) } @@ -630,6 +551,17 @@ extension JNISwift2JavaGenerator { conversion: .typeMetadataAddress(.placeholder), ) + case .tuple(let elements) where elements.count == 1: + return try translateParameter( + swiftType: elements[0].type, + parameterName: parameterName, + methodName: methodName, + parentName: parentName, + genericParameters: genericParameters, + genericRequirements: genericRequirements, + parameterPosition: parameterPosition, + ) + case .tuple(let elements) where !elements.isEmpty: return try translateTupleParameter( elements: elements, @@ -1023,6 +955,15 @@ extension JNISwift2JavaGenerator { case .tuple([]): return TranslatedResult(javaType: .void, nativeJavaType: .void, outParameters: [], conversion: .placeholder) + case .tuple(let elements) where elements.count == 1: + return try translateResult( + swiftType: elements[0].type, + methodName: methodName, + resultName: resultName, + genericParameters: genericParameters, + genericRequirements: genericRequirements, + ) + case .tuple(let elements) where !elements.isEmpty: return try translateTupleResult( methodName: methodName, @@ -1148,6 +1089,13 @@ extension JNISwift2JavaGenerator { } return .class(package: nil, name: generic.name) + case .tuple(let elements) where elements.count == 1: + return try translateGenericTypeParameter( + elements[0].type, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + case .tuple(let elements): let elementJavaTypes = try elements.map { element in try translateGenericTypeParameter( @@ -1225,7 +1173,6 @@ extension JNISwift2JavaGenerator { let javaNativeConversionStep: JavaNativeConversionStep = .tupleFromOutParams( - // try!-safe, because we know the result type is a class here (a Tuple of some form) tupleClassName: "\(javaResultType)", elements: tupleElements ) @@ -1623,50 +1570,42 @@ extension JNISwift2JavaGenerator { /// The corresponding Java case class (CamelCased) let name: String - /// The name of the translated enum - let enumName: String - /// The oringinal enum case. let original: ImportedEnumCase /// A list of the translated associated values - let translatedValues: [TranslatedParameter] - - /// A list of parameter conversions - let parameterConversions: [(translated: TranslatedResult, native: NativeResult)] + let parameters: [JavaParameter] - let getAsCaseFunction: TranslatedFunctionDecl + let getAsCaseFunction: ImportedFunc? - /// Returns whether the parameters require an arena - var requiresSwiftArena: Bool { - parameterConversions.contains(where: \.translated.conversion.requiresSwiftArena) - } + /// Returns whether the associated values require an arena + let requiresSwiftArena: Bool } struct TranslatedFunctionDecl { /// Java function name - let name: String + var name: String - let isStatic: Bool + var isStatic: Bool - let isThrowing: Bool + var isThrowing: Bool - let isAsync: Bool + var isAsync: Bool /// The name of the native function - let nativeFunctionName: String + var nativeFunctionName: String /// The name of the Java parent scope this function is declared in - let parentName: SwiftQualifiedTypeName + var parentName: SwiftQualifiedTypeName /// Functional interfaces required for the Java method. - let functionTypes: [TranslatedFunctionType] + var functionTypes: [TranslatedFunctionType] /// Function signature of the Java function the user will call - let translatedFunctionSignature: TranslatedFunctionSignature + var translatedFunctionSignature: TranslatedFunctionSignature /// Function signature of the native function that will be implemented by Swift - let nativeFunctionSignature: NativeFunctionSignature + var nativeFunctionSignature: NativeFunctionSignature /// Annotations to include on the Java function declaration var annotations: [JavaAnnotation] { @@ -1865,7 +1804,7 @@ extension JNISwift2JavaGenerator { /// E.g. `new Tuple2<>(result_0$[0], result_1$[0])` case tupleFromOutParams(tupleClassName: String, elements: [(outParamName: String, elementConversion: JavaNativeConversionStep)]) - indirect case placeToVar(JavaNativeConversionStep, name: String) + indirect case placeToVar(JavaNativeConversionStep, name: String, type: JavaType? = nil) /// `Arrays.stream(args)` static func arraysStream(_ argument: JavaNativeConversionStep) -> JavaNativeConversionStep { @@ -2035,9 +1974,10 @@ extension JNISwift2JavaGenerator { } return "new \(tupleClassName)(\(args.joined(separator: ", ")))" - case .placeToVar(let inner, let name): + case .placeToVar(let inner, let name, let type): let inner = inner.render(&printer, placeholder) - printer.print("var \(name) = \(inner);") + let type = type?.description ?? "var" + printer.print("\(type) \(name) = \(inner);") return name } } @@ -2105,7 +2045,7 @@ extension JNISwift2JavaGenerator { case .tupleFromOutParams(_, let elements): return elements.contains(where: { $0.elementConversion.requiresSwiftArena }) - case .placeToVar(let inner, _): + case .placeToVar(let inner, _, _): return inner.requiresSwiftArena } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 8bd5eb6d5..5335b81b1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -332,6 +332,16 @@ extension JNISwift2JavaGenerator { conversionCheck: nil ) + case .tuple(let elements) where elements.count == 1: + return try translateParameter( + type: elements[0].type, + parameterName: parameterName, + methodName: methodName, + parentName: parentName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + case .tuple(let elements) where !elements.isEmpty: return try translateTupleParameter( elements: elements, @@ -777,6 +787,9 @@ extension JNISwift2JavaGenerator { outParameters: [] ) + case .tuple(let elements) where elements.count == 1: + return try translateResult(swiftType: elements[0].type, methodName: methodName, resultName: resultName) + case .tuple(let elements) where !elements.isEmpty: return try translateTupleResult(methodName: methodName, elements: elements, resultName: resultName) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 3ad676be2..0d5246dc1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -173,23 +173,6 @@ extension JNISwift2JavaGenerator { logger.info("[swift-java] Generated linker export list (\(generatedCDeclSymbolNames.count) symbols): \(outputPath)") } - private func printJNICache(_ printer: inout CodePrinter, _ type: ImportedNominalType) { - let targetCases = type.cases - .compactMap(translatedEnumCase(for:)) - .filter { !$0.translatedValues.isEmpty } - if targetCases.isEmpty { - return - } - - printer.printBraceBlock("enum \(JNICaching.cacheName(for: type))") { printer in - for translatedCase in targetCases { - printer.print( - "static let \(JNICaching.cacheMemberName(for: translatedCase)) = \(renderEnumCaseCacheInit(translatedCase))" - ) - } - } - } - /// Prints the extension needed to make allow upcalls from Swift to Java for protocols private func printSwiftInterfaceWrapper( _ printer: inout CodePrinter, @@ -308,7 +291,6 @@ extension JNISwift2JavaGenerator { private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { printHeader(&printer) - printJNICache(&printer, type) printer.println() switch type.swiftNominal.kind { @@ -348,7 +330,7 @@ extension JNISwift2JavaGenerator { if !isEffectivelyGeneric { for enumCase in type.cases { - printEnumCase(&printer, enumCase) + printEnumCase(&printer, type, enumCase) printer.println() } } @@ -393,7 +375,7 @@ extension JNISwift2JavaGenerator { } } - private func printEnumCase(_ printer: inout CodePrinter, _ enumCase: ImportedEnumCase) { + private func printEnumCase(_ printer: inout CodePrinter, _ enumType: ImportedNominalType, _ enumCase: ImportedEnumCase) { guard let translatedCase = self.translatedEnumCase(for: enumCase) else { return } @@ -402,20 +384,7 @@ extension JNISwift2JavaGenerator { printSwiftFunctionThunk(&printer, enumCase.caseFunction) printer.println() - // Print getAsCase method - if !translatedCase.translatedValues.isEmpty { - printEnumGetAsCaseThunk(&printer, translatedCase) - } - } - - private func renderEnumCaseCacheInit(_ enumCase: TranslatedEnumCase) -> String { - let nativeParametersClassName = "\(enumCase.enumName)$Case$\(enumCase.name)$_NativeParameters" - let methodSignature = MethodSignature( - resultType: .void, - parameterTypes: enumCase.parameterConversions.map(\.native.javaType), - ) - - return renderJNICacheInit(className: nativeParametersClassName, methods: [("", methodSignature)]) + printEnumGetAsCaseThunk(&printer, enumType, translatedCase) } private func renderJNICacheInit(className: String, methods: [(String, MethodSignature)]) -> String { @@ -429,46 +398,26 @@ extension JNISwift2JavaGenerator { private func printEnumGetAsCaseThunk( _ printer: inout CodePrinter, + _ enumType: ImportedNominalType, _ enumCase: TranslatedEnumCase, ) { - printCDecl( - &printer, - enumCase.getAsCaseFunction, - ) { printer in - let selfPointer = enumCase.getAsCaseFunction.nativeFunctionSignature.selfParameter!.conversion.render( - &printer, - "selfPointer", - ) - let caseNames = enumCase.original.parameters.enumerated().map { idx, parameter in - parameter.name ?? "_\(idx)" - } - let caseNamesWithLet = caseNames.map { "let \($0)" } - let methodSignature = MethodSignature( - resultType: .void, - parameterTypes: enumCase.parameterConversions.map(\.native.javaType), - ) - printer.print( - """ - guard case .\(enumCase.original.name)(\(caseNamesWithLet.joined(separator: ", "))) = \(selfPointer).pointee else { - fatalError("Expected enum case '\(enumCase.original.name)', but was '\\(\(selfPointer).pointee)'!") + if let getAsCaseFunction = enumCase.getAsCaseFunction { + printer.printBraceBlock("extension \(enumType.effectiveSwiftTypeName)") { printer in + let associatedValueTypes = enumCase.original.parameters.map { param in + param.type.description + }.joined(separator: ", ") + printer.printBraceBlock("fileprivate func getAs\(enumCase.name)() -> (\(associatedValueTypes))?") { printer in + let params = enumCase.original.parameters.enumerated().map { i, param in + param.name ?? "_\(i)" + }.joined(separator: ", ") + printer.printBraceBlock("if case let .\(enumCase.original.name)(\(params)) = self") { printer in + printer.print("return (\(params))") + } + printer.print("return nil") } - let cache$ = \(JNICaching.cacheName(for: enumCase.original.enumType)).\(JNICaching.cacheMemberName(for: enumCase.original)) - let class$ = cache$.javaClass - let method$ = _JNIMethodIDCache.Method(name: "", signature: "\(methodSignature.mangledName)") - let constructorID$ = cache$[method$] - """ - ) - let upcallArguments = zip(enumCase.parameterConversions, caseNames).map { conversion, caseName in - let nullConversion = !conversion.native.javaType.isPrimitive ? " ?? nil" : "" - let result = conversion.native.conversion.render(&printer, caseName) - return "jvalue(\(conversion.native.javaType.jniFieldName): \(result)\(nullConversion))" } - printer.print( - """ - let newObjectArgs$: [jvalue] = [\(upcallArguments.joined(separator: ", "))] - return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) - """ - ) + + printSwiftFunctionThunk(&printer, getAsCaseFunction) } } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/tuple/Tuple1.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/tuple/Tuple1.java deleted file mode 100644 index db1baa8e4..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/tuple/Tuple1.java +++ /dev/null @@ -1,46 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 org.swift.swiftkit.core.tuple; - -/** - * Corresponds to Swift's built-in 1-element tuple type (T0). - * Elements are accessed via public final fields $0, $1, etc. - * @param the type of element 0 - */ -public class Tuple1 { - public final T0 $0; - - public Tuple1(T0 $0) { - this.$0 = $0; - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - if (!(other instanceof Tuple1)) return false; - Tuple1 o = (Tuple1) other; - return java.util.Objects.equals(this.$0, o.$0); - } - - @Override - public int hashCode() { - return java.util.Objects.hash($0); - } - - @Override - public String toString() { - return "Tuple1(" + $0 + ")"; - } -} diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index c202c95ae..ddb22bae8 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -20,11 +20,13 @@ import Testing @Suite struct JNIEnumTests { let source = """ - public enum MyEnum { - case first - case second(String) - case third(x: Int64, y: Int32) - } + public struct MyValue {} + + public enum MyEnum { + case first + case second(String) + case third(x: Int64, y: Int32, MyValue) + } """ @Test @@ -164,23 +166,17 @@ struct JNIEnumTests { expectedChunks: [ """ public sealed interface Case { - record First() implements Case { - record _NativeParameters() {} - } - record Second(java.lang.String arg0) implements Case { - record _NativeParameters(java.lang.String arg0) {} - } - record Third(long x, int y) implements Case { - record _NativeParameters(long x, int y) {} - } + record First() implements Case {} + record Second(java.lang.String arg0) implements Case {} + record Third(long x, int y, MyValue arg2) implements Case {} } """, """ - public Case getCase() { + public Case getCase(SwiftArena swiftArena) { return switch (this.getDiscriminator()) { - case FIRST -> this.getAsFirst().orElseThrow(); + case FIRST -> new Case.First(); case SECOND -> this.getAsSecond().orElseThrow(); - case THIRD -> this.getAsThird().orElseThrow(); + case THIRD -> this.getAsThird(swiftArena).orElseThrow(); } } """, @@ -207,8 +203,8 @@ struct JNIEnumTests { } """, """ - public static MyEnum third(long x, int y, SwiftArena swiftArena) { - return MyEnum.wrapMemoryAddressUnsafe(MyEnum.$third(x, y), swiftArena); + public static MyEnum third(long x, int y, MyValue arg2, SwiftArena swiftArena) { + return MyEnum.wrapMemoryAddressUnsafe(MyEnum.$third(x, y, arg2.$memoryAddress()), swiftArena); } """, ] @@ -242,10 +238,16 @@ struct JNIEnumTests { } """, """ - @_cdecl("Java_com_example_swift_MyEnum__00024third__JI") - public func Java_com_example_swift_MyEnum__00024third__JI(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jint) -> jlong { + @_cdecl("Java_com_example_swift_MyEnum__00024third__JIJ") + public func Java_com_example_swift_MyEnum__00024third__JIJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jint, arg2: jlong) -> jlong { + assert(arg2 != 0, "arg2 memory address was null") + let arg2Bits$ = Int(Int64(fromJNI: arg2, in: environment)) + let arg2$ = UnsafeMutablePointer(bitPattern: arg2Bits$) + guard let arg2$ else { + fatalError("arg2 memory address was null in call to \\(#function)!") + } let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment), y: Int32(fromJNI: y, in: environment))) + result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment), y: Int32(fromJNI: y, in: environment), arg2$.pointee)) let resultBits$ = Int64(Int(bitPattern: result$)) return resultBits$.getJNILocalRefValue(in: environment) } @@ -265,27 +267,27 @@ struct JNIEnumTests { """ public java.util.Optional getAsFirst() { if (getDiscriminator() != Discriminator.FIRST) { - return Optional.empty(); + return java.util.Optional.empty(); } - return Optional.of(new Case.First()); + return java.util.Optional.of(new Case.First()); } """, """ public java.util.Optional getAsSecond() { - if (getDiscriminator() != Discriminator.SECOND) { - return Optional.empty(); + ... + return associatedValues$.map((t) -> { + return new Case.Second(t); } - Case.Second._NativeParameters $nativeParameters = MyEnum.$getAsSecond(this.$memoryAddress()); - return Optional.of(new Case.Second($nativeParameters.arg0)); + ); } """, """ - public java.util.Optional getAsThird() { - if (getDiscriminator() != Discriminator.THIRD) { - return Optional.empty(); + public java.util.Optional getAsThird(SwiftArena swiftArena) { + ... + return associatedValues$.map((t) -> { + return new Case.Third(t.$0, t.$1, t.$2); } - Case.Third._NativeParameters $nativeParameters = MyEnum.$getAsThird(this.$memoryAddress()); - return Optional.of(new Case.Third($nativeParameters.x, $nativeParameters.y)); + ); } """, ] @@ -298,47 +300,37 @@ struct JNIEnumTests { input: source, .jni, .swift, - detectChunkByInitialLines: 1, + detectChunkByInitialLines: 2, expectedChunks: [ """ - enum _JNI_MyEnum { - static let myEnumSecondCache = _JNIMethodIDCache(className: "com/example/swift/MyEnum$Case$Second$_NativeParameters", methods: [.init(name: "", signature: "(Ljava/lang/String;)V")]) - static let myEnumThirdCache = _JNIMethodIDCache(className: "com/example/swift/MyEnum$Case$Third$_NativeParameters", methods: [.init(name: "", signature: "(JI)V")]) + extension MyEnum { + fileprivate func getAsSecond() -> (String)? { + if case let .second(_0) = self { + return (_0) + } + return nil + } } """, """ - @_cdecl("Java_com_example_swift_MyEnum__00024getAsSecond__J") - public func Java_com_example_swift_MyEnum__00024getAsSecond__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jobject? { - ... - guard case .second(let _0) = selfPointer$.pointee else { - fatalError("Expected enum case 'second', but was '\\(selfPointer$.pointee)'!") + extension MyEnum { + fileprivate func getAsThird() -> (Int64, Int32, MyValue)? { + if case let .third(x, y, _2) = self { + return (x, y, _2) + } + return nil } - let cache$ = _JNI_MyEnum.myEnumSecondCache - let class$ = cache$.javaClass - let method$ = _JNIMethodIDCache.Method(name: "", signature: "(Ljava/lang/String;)V") - let constructorID$ = cache$[method$] - let newObjectArgs$: [jvalue] = [jvalue(l: _0.getJNILocalRefValue(in: environment) ?? nil)] - return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) } """, """ - @_cdecl("Java_com_example_swift_MyEnum__00024getAsThird__J") - public func Java_com_example_swift_MyEnum__00024getAsThird__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jobject? { - ... - guard case .third(let x, let y) = selfPointer$.pointee else { - fatalError("Expected enum case 'third', but was '\\(selfPointer$.pointee)'!") - } - let cache$ = _JNI_MyEnum.myEnumThirdCache - let class$ = cache$.javaClass - let method$ = _JNIMethodIDCache.Method(name: "", signature: "(JI)V") - let constructorID$ = cache$[method$] - let newObjectArgs$: [jvalue] = [jvalue(j: x.getJNILocalRefValue(in: environment)), jvalue(i: y.getJNILocalRefValue(in: environment))] - return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) - } + public func Java_com_example_swift_MyEnum__00024getAsSecond__J_3B + """, + """ + public func Java_com_example_swift_MyEnum__00024getAsThird__J_3B_3J_3I_3J """, ], notExpectedChunks: [ - "public func Java_com_example_swift_MyEnum__00024getAsFirst__J(" + "fileprivate func getAsFirst(" ] ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index 7c97ad398..e0c148d10 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -213,4 +213,43 @@ struct JNIGenericTypeTests { ] ) } + + @Test + func genericValueInEnumCase() throws { + let input = + #""" + public struct MyID {} + + public enum MyEnum { + case foo(MyID) + } + """# + + try assertOutput( + input: input, + .jni, + .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + #""" + public sealed interface Case { + record Foo(MyID arg0) implements Case {} + } + """# + ] + ) + + + try assertOutput( + input: input, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + #""" + public func Java_com_example_swift_MyEnum__00024getAsFoo__J_3BLorg_swift_swiftkit_core__1OutSwiftGenericInstance_2(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong, result_discriminator$: jbyteArray?, resultWrappedOut: jobject?) { + """# + ] + ) + } } diff --git a/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift b/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift index ef90053fa..3e49daf08 100644 --- a/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift @@ -174,4 +174,61 @@ struct JNITupleTests { ] ) } + + @Test + func singleTuple() throws { + let input = """ + public func singleTuple(input: (String)) -> (String) { + input + } + """ + + try assertOutput( + input: input, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + private static native java.lang.String $singleTuple(java.lang.String input); + """ + ] + ) + + try assertOutput( + input: input, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public func Java_com_example_swift_SwiftModule__00024singleTuple__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, input: jstring?) -> jstring? { + return SwiftModule.singleTuple(input: String(fromJNI: input, in: environment)).getJNILocalRefValue(in: environment) + } + """ + ] + ) + } + + @Test + func singleTupleInGeneric() throws { + let input = """ + public struct Box {} + public var singleTupleInGeneric: Box<(String)> { + "input" + } + """ + + try assertOutput( + input: input, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static Box getSingleTupleInGeneric(SwiftArena swiftArena) + """ + ] + ) + } } diff --git a/Tests/JExtractSwiftTests/JavaKeywordTests.swift b/Tests/JExtractSwiftTests/JavaKeywordTests.swift index 135eda574..81588be3b 100644 --- a/Tests/JExtractSwiftTests/JavaKeywordTests.swift +++ b/Tests/JExtractSwiftTests/JavaKeywordTests.swift @@ -98,7 +98,7 @@ struct JavaKeywordTests { record Instanceof(java.lang.String arg0) implements Case { """, """ - private static native Case.Instanceof._NativeParameters $getAsInstanceof(long selfPointer); + public java.util.Optional getAsInstanceof() { """, ] ) @@ -109,7 +109,8 @@ struct JavaKeywordTests { .swift, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_MyEnumWithValue__00024getAsInstanceof__J") + extension MyEnumWithValue { + fileprivate func getAsInstanceof() -> (String)? { """ ] )