diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift new file mode 100644 index 00000000..673ecb0b --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import JavaKit + +public func optionalBool(input: Optional) -> Bool? { + return input +} + +public func optionalByte(input: Optional) -> Int8? { + return input +} + +public func optionalChar(input: Optional) -> UInt16? { + return input +} + +public func optionalShort(input: Optional) -> Int16? { + return input +} + +public func optionalInt(input: Optional) -> Int32? { + return input +} + +public func optionalLong(input: Optional) -> Int64? { + return input +} + +public func optionalFloat(input: Optional) -> Float? { + return input +} + +public func optionalDouble(input: Optional) -> Double? { + return input +} + +public func optionalString(input: Optional) -> String? { + return input +} + +public func optionalClass(input: Optional) -> MySwiftClass? { + return input +} + +public func optionalJavaKitLong(input: Optional) -> Int64? { + if let input { + return input.longValue() + } else { + return nil + } +} + +public func multipleOptionals( + input1: Optional, + input2: Optional, + input3: Optional, + input4: Optional, + input5: Optional, + input6: Optional, + input7: Optional +) -> Int64? { + return 1 +} diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index f034b904..e7de03ad 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -17,6 +17,10 @@ import org.junit.jupiter.api.Test; import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.OptionalLong; + import static org.junit.jupiter.api.Assertions.*; public class MySwiftClassTest { diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java new file mode 100644 index 00000000..f7262ad4 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java @@ -0,0 +1,116 @@ +//===----------------------------------------------------------------------===// +// +// 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 com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OptionalsTest { + @Test + void optionalBool() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalBool(Optional.empty())); + assertEquals(Optional.of(true), MySwiftLibrary.optionalBool(Optional.of(true))); + } + + @Test + void optionalByte() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalByte(Optional.empty())); + assertEquals(Optional.of((byte) 1) , MySwiftLibrary.optionalByte(Optional.of((byte) 1))); + } + + @Test + void optionalChar() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalChar(Optional.empty())); + assertEquals(Optional.of((char) 42), MySwiftLibrary.optionalChar(Optional.of((char) 42))); + } + + @Test + void optionalShort() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalShort(Optional.empty())); + assertEquals(Optional.of((short) -250), MySwiftLibrary.optionalShort(Optional.of((short) -250))); + } + + @Test + void optionalInt() { + assertEquals(OptionalInt.empty(), MySwiftLibrary.optionalInt(OptionalInt.empty())); + assertEquals(OptionalInt.of(999), MySwiftLibrary.optionalInt(OptionalInt.of(999))); + } + + @Test + void optionalLong() { + assertEquals(OptionalLong.empty(), MySwiftLibrary.optionalLong(OptionalLong.empty())); + assertEquals(OptionalLong.of(999), MySwiftLibrary.optionalLong(OptionalLong.of(999))); + } + + @Test + void optionalFloat() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalFloat(Optional.empty())); + assertEquals(Optional.of(3.14f), MySwiftLibrary.optionalFloat(Optional.of(3.14f))); + } + + @Test + void optionalDouble() { + assertEquals(OptionalDouble.empty(), MySwiftLibrary.optionalDouble(OptionalDouble.empty())); + assertEquals(OptionalDouble.of(2.718), MySwiftLibrary.optionalDouble(OptionalDouble.of(2.718))); + } + + @Test + void optionalString() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalString(Optional.empty())); + assertEquals(Optional.of("Hello Swift!"), MySwiftLibrary.optionalString(Optional.of("Hello Swift!"))); + } + + @Test + void optionalClass() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(arena); + assertEquals(Optional.empty(), MySwiftLibrary.optionalClass(Optional.empty(), arena)); + Optional optionalClass = MySwiftLibrary.optionalClass(Optional.of(c), arena); + assertTrue(optionalClass.isPresent()); + assertEquals(c.getX(), optionalClass.get().getX()); + } + } + + @Test + void optionalJavaKitLong() { + assertEquals(OptionalLong.empty(), MySwiftLibrary.optionalJavaKitLong(Optional.empty())); + assertEquals(OptionalLong.of(99L), MySwiftLibrary.optionalJavaKitLong(Optional.of(99L))); + } + + @Test + void multipleOptionals() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(arena); + OptionalLong result = MySwiftLibrary.multipleOptionals( + Optional.of((byte) 1), + Optional.of((short) 42), + OptionalInt.of(50), + OptionalLong.of(1000L), + Optional.of("42"), + Optional.of(c), + Optional.of(true) + ); + assertEquals(result, OptionalLong.of(1L)); + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index f9a67419..cb849e79 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -36,4 +36,79 @@ extension JavaType { case .void: fatalError("There is no type signature for 'void'") } } + + /// Returns the next integral type with space for self and an additional byte. + var nextIntergralTypeWithSpaceForByte: (javaType: JavaType, swiftType: SwiftKnownTypeDeclKind, valueBytes: Int)? { + switch self { + case .boolean, .byte: (.short, .int16, 1) + case .char, .short: (.int, .int32, 2) + case .int: (.long, .int64, 4) + default: nil + } + } + + var optionalType: String? { + switch self { + case .boolean: "Optional" + case .byte: "Optional" + case .char: "Optional" + case .short: "Optional" + case .int: "OptionalInt" + case .long: "OptionalLong" + case .float: "Optional" + case .double: "OptionalDouble" + case .javaLangString: "Optional" + default: nil + } + } + + var optionalWrapperType: String? { + switch self { + case .boolean, .byte, .char, .short, .float, .javaLangString: "Optional" + case .int: "OptionalInt" + case .long: "OptionalLong" + case .double: "OptionalDouble" + default: nil + } + } + + var optionalPlaceholderValue: String? { + switch self { + case .boolean: "false" + case .byte: "(byte) 0" + case .char: "(char) 0" + case .short: "(short) 0" + case .int: "0" + case .long: "0L" + case .float: "0f" + case .double: "0.0" + case .array, .class: "null" + case .void: nil + } + } + + var jniCallMethodAName: String { + switch self { + case .boolean: "CallBooleanMethodA" + case .byte: "CallByteMethodA" + case .char: "CallCharMethodA" + case .short: "CallShortMethodA" + case .int: "CallIntMethodA" + case .long: "CallLongMethodA" + case .float: "CallFloatMethodA" + case .double: "CallDoubleMethodA" + case .void: "CallVoidMethodA" + default: "CallObjectMethodA" + } + } + + /// Returns whether this type returns `JavaValue` from JavaKit + var implementsJavaValue: Bool { + return switch self { + case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString: + true + default: + false + } + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 63b9dcd1..fe6b156d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import JavaTypes // MARK: Defaults @@ -20,6 +21,7 @@ extension JNISwift2JavaGenerator { static let defaultJavaImports: Array = [ "org.swift.swiftkit.core.*", "org.swift.swiftkit.core.util.*", + "java.util.*", // NonNull, Unsigned and friends "org.swift.swiftkit.core.annotations.*", @@ -276,11 +278,15 @@ extension JNISwift2JavaGenerator { let translatedDecl = translatedDecl(for: decl)! // Will always call with valid decl let nativeSignature = translatedDecl.nativeFunctionSignature let resultType = nativeSignature.result.javaType - var parameters = nativeSignature.parameters - if let selfParameter = nativeSignature.selfParameter { - parameters.append(selfParameter) + var parameters = nativeSignature.parameters.flatMap(\.parameters) + if let selfParameter = nativeSignature.selfParameter?.parameters { + parameters += selfParameter } - let renderedParameters = parameters.map { "\($0.javaType) \($0.name)"}.joined(separator: ", ") + parameters += nativeSignature.result.outParameters + + let renderedParameters = parameters.map { javaParameter in + "\(javaParameter.type) \(javaParameter.name)" + }.joined(separator: ", ") printer.print("private static native \(resultType) \(translatedDecl.nativeFunctionName)(\(renderedParameters));") } @@ -305,6 +311,12 @@ extension JNISwift2JavaGenerator { arguments.append(lowered) } + // Indirect return receivers + for outParameter in translatedFunctionSignature.resultType.outParameters { + printer.print("\(outParameter.type) \(outParameter.name) = \(outParameter.allocation.render());") + arguments.append(outParameter.name) + } + //=== Part 3: Downcall. // TODO: If we always generate a native method and a "public" method, we can actually choose our own thunk names // using the registry? diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 5908cfb1..0db77ece 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -190,14 +190,26 @@ extension JNISwift2JavaGenerator { let nominalTypeName = nominalType.nominalTypeDecl.name if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { - throw JavaTranslationError.unsupportedSwiftType(swiftType) + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + return try translateOptionalParameter( + wrappedType: genericArgs[0], + parameterName: parameterName + ) + + default: + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: javaType, annotations: parameterAnnotations), + conversion: .placeholder + ) } - - return TranslatedParameter( - parameter: JavaParameter(name: parameterName, type: javaType, annotations: parameterAnnotations), - conversion: .placeholder - ) } if nominalType.isJavaKitWrapper { @@ -237,13 +249,22 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: + case .optional(let wrapped): + return try translateOptionalParameter( + wrappedType: wrapped, + parameterName: parameterName + ) + + case .metatype, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } - func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType, - mode: JExtractUnsignedIntegerMode) -> JavaNativeConversionStep { + func unsignedResultConversion( + _ from: SwiftType, + to javaType: JavaType, + mode: JExtractUnsignedIntegerMode + ) -> JavaNativeConversionStep { switch mode { case .annotate: return .placeholder // no conversions @@ -253,6 +274,75 @@ extension JNISwift2JavaGenerator { } } + func translateOptionalParameter( + wrappedType swiftType: SwiftType, + parameterName: String + ) throws -> TranslatedParameter { + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + switch swiftType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + guard let translatedClass = javaType.optionalType, let placeholderValue = javaType.optionalPlaceholderValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: JavaType(className: translatedClass), + annotations: parameterAnnotations + ), + conversion: .commaSeparated([ + .isOptionalPresent, + .method(.placeholder, function: "orElse", arguments: [.constant(placeholderValue)]) + ]) + ) + } + + if nominalType.isJavaKitWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftType) + } + + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .class(package: nil, name: "Optional<\(javaType)>"), + annotations: parameterAnnotations + ), + conversion: .method( + .placeholder, + function: "orElse", + arguments: [.constant("null")] + ) + ) + } + + // Assume JExtract imported class + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .class(package: nil, name: "Optional<\(nominalTypeName)>"), + annotations: parameterAnnotations + ), + conversion: .method( + .method(.placeholder, function: "map", arguments: [.constant("\(nominalType)::$memoryAddress")]), + function: "orElse", + arguments: [.constant("0L")] + ) + ) + default: + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + } + func translate(swiftResult: SwiftResult) throws -> TranslatedResult { let swiftType = swiftResult.type @@ -262,15 +352,25 @@ extension JNISwift2JavaGenerator { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { - throw JavaTranslationError.unsupportedSwiftType(swiftType) + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + return try translateOptionalResult(wrappedType: genericArgs[0]) + + default: + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + return TranslatedResult( + javaType: javaType, + annotations: resultAnnotations, + outParameters: [], + conversion: .placeholder + ) } - - return TranslatedResult( - javaType: javaType, - annotations: resultAnnotations, - conversion: .placeholder - ) } if nominalType.isJavaKitWrapper { @@ -282,13 +382,94 @@ extension JNISwift2JavaGenerator { return TranslatedResult( javaType: javaType, annotations: resultAnnotations, + outParameters: [], conversion: .constructSwiftValue(.placeholder, javaType) ) case .tuple([]): - return TranslatedResult(javaType: .void, conversion: .placeholder) + return TranslatedResult(javaType: .void, outParameters: [], conversion: .placeholder) + + case .optional(let wrapped): + return try translateOptionalResult(wrappedType: wrapped) - case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + } + + func translateOptionalResult( + wrappedType swiftType: SwiftType + ) throws -> TranslatedResult { + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + switch swiftType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + guard let returnType = javaType.optionalType, let optionalClass = javaType.optionalWrapperType else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + // Check if we can fit the value and a discriminator byte in a primitive. + // so the return JNI value will be (value, discriminator) + if let nextIntergralTypeWithSpaceForByte = javaType.nextIntergralTypeWithSpaceForByte { + return TranslatedResult( + javaType: .class(package: nil, name: returnType), + annotations: parameterAnnotations, + outParameters: [], + conversion: .combinedValueToOptional( + .placeholder, + nextIntergralTypeWithSpaceForByte.javaType, + valueType: javaType, + valueSizeInBytes: nextIntergralTypeWithSpaceForByte.valueBytes, + optionalType: optionalClass + ) + ) + } else { + // Otherwise, we return the result as normal, but + // use an indirect return for the discriminator. + return TranslatedResult( + javaType: .class(package: nil, name: returnType), + annotations: parameterAnnotations, + outParameters: [ + OutParameter(name: "result_discriminator$", type: .array(.byte), allocation: .newArray(.byte, size: 1)) + ], + conversion: .toOptionalFromIndirectReturn( + discriminatorName: "result_discriminator$", + optionalClass: optionalClass, + javaType: javaType, + toValue: .placeholder + ) + ) + } + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + // We assume this is a JExtract class. + let returnType = JavaType.class(package: nil, name: "Optional<\(nominalTypeName)>") + return TranslatedResult( + javaType: returnType, + annotations: parameterAnnotations, + outParameters: [ + OutParameter(name: "result_discriminator$", type: .array(.byte), allocation: .newArray(.byte, size: 1)) + ], + conversion: .toOptionalFromIndirectReturn( + discriminatorName: "result_discriminator$", + optionalClass: "Optional", + javaType: .long, + toValue: .constructSwiftValue(.placeholder, .class(package: nil, name: nominalTypeName)) + ) + ) + + default: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -348,10 +529,33 @@ extension JNISwift2JavaGenerator { /// Java annotations that should be propagated from the result type onto the method var annotations: [JavaAnnotation] = [] + let outParameters: [OutParameter] + /// Represents how to convert the Java native result into a user-facing result. let conversion: JavaNativeConversionStep } + struct OutParameter { + enum Allocation { + case newArray(JavaType, size: Int) + + func render() -> String { + switch self { + case .newArray(let javaType, let size): + "new \(javaType)[\(size)]" + } + } + } + + let name: String + let type: JavaType + let allocation: Allocation + + var javaParameter: JavaParameter { + JavaParameter(name: self.name, type: self.type) + } + } + /// Represent a Swift closure type in the user facing Java API. /// /// Closures are translated to named functional interfaces in Java. @@ -367,6 +571,11 @@ extension JNISwift2JavaGenerator { /// The value being converted case placeholder + case constant(String) + + // Convert the results of the inner steps to a comma separated list. + indirect case commaSeparated([JavaNativeConversionStep]) + /// `value.$memoryAddress()` indirect case valueMemoryAddress(JavaNativeConversionStep) @@ -375,6 +584,43 @@ extension JNISwift2JavaGenerator { indirect case call(JavaNativeConversionStep, function: String) + indirect case method(JavaNativeConversionStep, function: String, arguments: [JavaNativeConversionStep] = []) + + case isOptionalPresent + + indirect case combinedValueToOptional(JavaNativeConversionStep, JavaType, valueType: JavaType, valueSizeInBytes: Int, optionalType: String) + + indirect case ternary(JavaNativeConversionStep, thenExp: JavaNativeConversionStep, elseExp: JavaNativeConversionStep) + + indirect case equals(JavaNativeConversionStep, JavaNativeConversionStep) + + indirect case subscriptOf(JavaNativeConversionStep, arguments: [JavaNativeConversionStep]) + + static func toOptionalFromIndirectReturn( + discriminatorName: String, + optionalClass: String, + javaType: JavaType, + toValue valueConversion: JavaNativeConversionStep + ) -> JavaNativeConversionStep { + .aggregate( + name: "result$", + type: javaType, + [ + .ternary( + .equals( + .subscriptOf(.constant(discriminatorName), arguments: [.constant("0")]), + .constant("1") + ), + thenExp: .method(.constant(optionalClass), function: "of", arguments: [valueConversion]), + elseExp: .method(.constant(optionalClass), function: "empty") + ) + ] + ) + } + + /// Perform multiple conversions using the same input. + case aggregate(name: String, type: JavaType, [JavaNativeConversionStep]) + /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { // NOTE: 'printer' is used if the conversion wants to cause side-effects. @@ -383,6 +629,12 @@ extension JNISwift2JavaGenerator { case .placeholder: return placeholder + case .constant(let value): + return value + + case .commaSeparated(let list): + return list.map({ $0.render(&printer, placeholder)}).joined(separator: ", ") + case .valueMemoryAddress: return "\(placeholder).$memoryAddress()" @@ -394,13 +646,63 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(function)(\(inner))" + + case .isOptionalPresent: + return "(byte) (\(placeholder).isPresent() ? 1 : 0)" + + case .method(let inner, let methodName, let arguments): + let inner = inner.render(&printer, placeholder) + let args = arguments.map { $0.render(&printer, placeholder) } + let argsStr = args.joined(separator: ", ") + return "\(inner).\(methodName)(\(argsStr))" + + case .combinedValueToOptional(let combined, let combinedType, let valueType, let valueSizeInBytes, let optionalType): + let combined = combined.render(&printer, placeholder) + printer.print( + """ + \(combinedType) combined$ = \(combined); + byte discriminator$ = (byte) (combined$ & 0xFF); + """ + ) + + if valueType == .boolean { + printer.print("boolean value$ = ((byte) (combined$ >> 8)) != 0;") + } else { + printer.print("\(valueType) value$ = (\(valueType)) (combined$ >> \(valueSizeInBytes * 8));") + } + + return "discriminator$ == 1 ? \(optionalType).of(value$) : \(optionalType).empty()" + + case .ternary(let cond, let thenExp, let elseExp): + let cond = cond.render(&printer, placeholder) + let thenExp = thenExp.render(&printer, placeholder) + let elseExp = elseExp.render(&printer, placeholder) + return "(\(cond)) ? \(thenExp) : \(elseExp)" + + case .equals(let lhs, let rhs): + let lhs = lhs.render(&printer, placeholder) + let rhs = rhs.render(&printer, placeholder) + return "\(lhs) == \(rhs)" + + case .subscriptOf(let inner, let arguments): + let inner = inner.render(&printer, placeholder) + let arguments = arguments.map { $0.render(&printer, placeholder) } + return "\(inner)[\(arguments.joined(separator: ", "))]" + + case .aggregate(let name, let type, let steps): + precondition(!steps.isEmpty, "Aggregate must contain steps") + printer.print("\(type) \(name) = \(placeholder);") + let steps = steps.map { + $0.render(&printer, name) + } + return steps.last! } } /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder: + case .placeholder, .constant, .isOptionalPresent: return false case .constructSwiftValue: @@ -409,6 +711,27 @@ extension JNISwift2JavaGenerator { case .valueMemoryAddress(let inner): return inner.requiresSwiftArena + case .commaSeparated(let list): + return list.contains(where: { $0.requiresSwiftArena }) + + case .method(let inner, _, let args): + return inner.requiresSwiftArena || args.contains(where: \.requiresSwiftArena) + + case .combinedValueToOptional(let inner, _, _, _, _): + return inner.requiresSwiftArena + + case .ternary(let cond, let thenExp, let elseExp): + return cond.requiresSwiftArena || thenExp.requiresSwiftArena || elseExp.requiresSwiftArena + + case .equals(let lhs, let rhs): + return lhs.requiresSwiftArena || rhs.requiresSwiftArena + + case .subscriptOf(let inner, _): + return inner.requiresSwiftArena + + case .aggregate(_, _, let steps): + return steps.contains(where: \.requiresSwiftArena) + case .call(let inner, _): return inner.requiresSwiftArena } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 9f1113fc..e7f7efbe 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -72,16 +72,30 @@ extension JNISwift2JavaGenerator { let nominalTypeName = nominalType.nominalTypeDecl.name if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), - javaType.implementsJavaValue else { - throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) - } + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + } + return try translateOptionalParameter( + wrappedType: genericArgs[0], + parameterName: parameterName + ) + + default: + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + } + + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: javaType) + ], + conversion: .initFromJNI(.placeholder, swiftType: swiftParameter.type) + ) - return NativeParameter( - name: parameterName, - javaType: javaType, - conversion: .initFromJNI(.placeholder, swiftType: swiftParameter.type) - ) + } } if nominalType.isJavaKitWrapper { @@ -90,23 +104,33 @@ extension JNISwift2JavaGenerator { } return NativeParameter( - name: parameterName, - javaType: javaType, - conversion: .initializeJavaKitWrapper(wrapperName: nominalTypeName) + parameters: [ + JavaParameter(name: parameterName, type: javaType) + ], + conversion: .initializeJavaKitWrapper( + .unwrapOptional( + .placeholder, + name: parameterName, + fatalErrorMessage: "\(parameterName) was null in call to \\(#function), but Swift requires non-optional!" + ), + wrapperName: nominalTypeName + ) ) } // JExtract classes are passed as the pointer. return NativeParameter( - name: parameterName, - javaType: .long, + parameters: [ + JavaParameter(name: parameterName, type: .long) + ], conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: swiftParameter.type)) ) case .tuple([]): return NativeParameter( - name: parameterName, - javaType: .void, + parameters: [ + JavaParameter(name: parameterName, type: .void) + ], conversion: .placeholder ) @@ -124,19 +148,162 @@ extension JNISwift2JavaGenerator { let result = try translateClosureResult(fn.resultType) return NativeParameter( - name: parameterName, - javaType: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)"), + parameters: [ + JavaParameter(name: parameterName, type: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)")) + ], conversion: .closureLowering( parameters: parameters, result: result ) ) - case .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: + case .optional(let wrapped): + return try translateOptionalParameter( + wrappedType: wrapped, + parameterName: parameterName + ) + + case .metatype, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) } } + func translateOptionalParameter( + wrappedType swiftType: SwiftType, + parameterName: String + ) throws -> NativeParameter { + let discriminatorName = "\(parameterName)_discriminator" + let valueName = "\(parameterName)_value" + + switch swiftType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + return NativeParameter( + parameters: [ + JavaParameter(name: discriminatorName, type: .byte), + JavaParameter(name: valueName, type: javaType) + ], + conversion: .optionalLowering( + .initFromJNI(.placeholder, swiftType: swiftType), + discriminatorName: discriminatorName, + valueName: valueName + ) + ) + } + + if nominalType.isJavaKitWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftType) + } + + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: javaType) + ], + conversion: .optionalMap(.initializeJavaKitWrapper(.placeholder, wrapperName: nominalTypeName)) + ) + } + + // Assume JExtract wrapped class + return NativeParameter( + parameters: [JavaParameter(name: parameterName, type: .long)], + conversion: .pointee( + .optionalChain( + .extractSwiftValue( + .placeholder, + swiftType: swiftType, + allowNil: true + ) + ) + ) + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + } + + func translateOptionalResult( + wrappedType swiftType: SwiftType + ) throws -> NativeResult { + switch swiftType { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + // Check if we can fit the value and a discriminator byte in a primitive. + // so the return JNI value will be (value, discriminator) + if let nextIntergralTypeWithSpaceForByte = javaType.nextIntergralTypeWithSpaceForByte { + return NativeResult( + javaType: nextIntergralTypeWithSpaceForByte.javaType, + conversion: .getJNIValue( + .optionalRaisingWidenIntegerType( + .placeholder, + valueType: javaType, + combinedSwiftType: nextIntergralTypeWithSpaceForByte.swiftType, + valueSizeInBytes: nextIntergralTypeWithSpaceForByte.valueBytes + ) + ), + outParameters: [] + ) + } else { + // Use indirect byte array to store discriminator + let discriminatorName = "result_discriminator$" + + return NativeResult( + javaType: javaType, + conversion: .optionalRaisingIndirectReturn( + .getJNIValue(.placeholder), + returnType: javaType, + discriminatorParameterName: discriminatorName, + placeholderValue: .member( + .constant("\(swiftType)"), + member: "jniPlaceholderValue" + ) + ), + outParameters: [ + JavaParameter(name: discriminatorName, type: .array(.byte)) + ] + ) + } + } + + guard !nominalType.isJavaKitWrapper else { + // TODO: Should be the same as above + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + // Assume JExtract imported class + let discriminatorName = "result_discriminator$" + + return NativeResult( + javaType: .long, + conversion: .optionalRaisingIndirectReturn( + .getJNIValue(.allocateSwiftValue(name: "_result", swiftType: swiftType)), + returnType: .long, + discriminatorParameterName: discriminatorName, + placeholderValue: .constant("0") + ), + outParameters: [ + JavaParameter(name: discriminatorName, type: .array(.byte)) + ] + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + } + func translateClosureResult( _ type: SwiftType ) throws -> NativeResult { @@ -151,7 +318,8 @@ extension JNISwift2JavaGenerator { // Only support primitives for now. return NativeResult( javaType: javaType, - conversion: .initFromJNI(.placeholder, swiftType: type) + conversion: .initFromJNI(.placeholder, swiftType: type), + outParameters: [] ) } @@ -161,7 +329,8 @@ extension JNISwift2JavaGenerator { case .tuple([]): return NativeResult( javaType: .void, - conversion: .placeholder + conversion: .placeholder, + outParameters: [] ) case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: @@ -183,8 +352,9 @@ extension JNISwift2JavaGenerator { // Only support primitives for now. return NativeParameter( - name: parameterName, - javaType: javaType, + parameters: [ + JavaParameter(name: parameterName, type: javaType) + ], conversion: .getJValue(.placeholder) ) } @@ -203,17 +373,24 @@ extension JNISwift2JavaGenerator { switch swiftResult.type { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + return try translateOptionalResult(wrappedType: genericArgs[0]) + + default: + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + + return NativeResult( + javaType: javaType, + conversion: .getJNIValue(.placeholder), + outParameters: [] + ) } - guard javaType.implementsJavaValue else { - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) - } - - return NativeResult( - javaType: javaType, - conversion: .getJNIValue(.placeholder) - ) } if nominalType.isJavaKitWrapper { @@ -222,20 +399,23 @@ extension JNISwift2JavaGenerator { return NativeResult( javaType: .long, - conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)) + conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)), + outParameters: [] ) case .tuple([]): return NativeResult( javaType: .void, - conversion: .placeholder + conversion: .placeholder, + outParameters: [] ) - case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: + case .optional(let wrapped): + return try translateOptionalResult(wrappedType: wrapped) + + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } - - } } @@ -246,12 +426,9 @@ extension JNISwift2JavaGenerator { } struct NativeParameter { - let name: String - let javaType: JavaType - - var jniType: JNIType { - javaType.jniType - } + /// One Swift parameter can be lowered to multiple parameters. + /// E.g. 'Optional' as (descriptor, value) pair. + var parameters: [JavaParameter] /// Represents how to convert the JNI parameter to a Swift parameter let conversion: NativeSwiftConversionStep @@ -260,6 +437,9 @@ extension JNISwift2JavaGenerator { struct NativeResult { let javaType: JavaType let conversion: NativeSwiftConversionStep + + /// Out parameters for populating the indirect return values. + var outParameters: [JavaParameter] } /// Describes how to convert values between Java types and Swift through JNI @@ -267,6 +447,8 @@ extension JNISwift2JavaGenerator { /// The value being converted case placeholder + case constant(String) + /// `value.getJNIValue(in:)` indirect case getJNIValue(NativeSwiftConversionStep) @@ -277,7 +459,11 @@ extension JNISwift2JavaGenerator { indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) /// Extracts a swift type at a pointer given by a long. - indirect case extractSwiftValue(NativeSwiftConversionStep, swiftType: SwiftType) + indirect case extractSwiftValue( + NativeSwiftConversionStep, + swiftType: SwiftType, + allowNil: Bool = false + ) /// Allocate memory for a Swift value and outputs the pointer case allocateSwiftValue(name: String, swiftType: SwiftType) @@ -288,7 +474,23 @@ extension JNISwift2JavaGenerator { indirect case closureLowering(parameters: [NativeParameter], result: NativeResult) - case initializeJavaKitWrapper(wrapperName: String) + indirect case initializeJavaKitWrapper(NativeSwiftConversionStep, wrapperName: String) + + indirect case optionalLowering(NativeSwiftConversionStep, discriminatorName: String, valueName: String) + + indirect case optionalChain(NativeSwiftConversionStep) + + indirect case optionalRaisingWidenIntegerType(NativeSwiftConversionStep, valueType: JavaType, combinedSwiftType: SwiftKnownTypeDeclKind, valueSizeInBytes: Int) + + indirect case optionalRaisingIndirectReturn(NativeSwiftConversionStep, returnType: JavaType, discriminatorParameterName: String, placeholderValue: NativeSwiftConversionStep) + + indirect case method(NativeSwiftConversionStep, function: String, arguments: [(String?, NativeSwiftConversionStep)] = []) + + indirect case member(NativeSwiftConversionStep, member: String) + + indirect case optionalMap(NativeSwiftConversionStep) + + indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String) /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { @@ -298,6 +500,9 @@ extension JNISwift2JavaGenerator { case .placeholder: return placeholder + case .constant(let value): + return value + case .getJNIValue(let inner): let inner = inner.render(&printer, placeholder) return "\(inner).getJNIValue(in: environment!)" @@ -310,18 +515,28 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(swiftType)(fromJNI: \(inner), in: environment!)" - case .extractSwiftValue(let inner, let swiftType): + case .extractSwiftValue(let inner, let swiftType, let allowNil): let inner = inner.render(&printer, placeholder) + let pointerName = "\(inner)$" + if !allowNil { + printer.print(#"assert(\#(inner) != 0, "\#(inner) memory address was null")"#) + } printer.print( """ - assert(\(inner) != 0, "\(inner) memory address was null") let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment!)) - guard let \(inner)$ = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) else { - fatalError("\(inner) memory address was null in call to \\(#function)!") - } + let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) """ ) - return "\(inner)$" + if !allowNil { + printer.print( + """ + guard let \(pointerName) else { + fatalError("\(inner) memory address was null in call to \\(#function)!") + } + """ + ) + } + return pointerName case .allocateSwiftValue(let name, let swiftType): let pointerName = "\(name)$" @@ -344,15 +559,17 @@ extension JNISwift2JavaGenerator { let methodSignature = MethodSignature( resultType: nativeResult.javaType, - parameterTypes: parameters.map(\.javaType) + parameterTypes: parameters.flatMap { $0.parameters.map(\.type) } ) - let closureParameters = !parameters.isEmpty ? "\(parameters.map(\.name).joined(separator: ", ")) in" : "" + let names = parameters.flatMap { $0.parameters.map(\.name) } + let closureParameters = !parameters.isEmpty ? "\(names.joined(separator: ", ")) in" : "" printer.print("{ \(closureParameters)") printer.indent() + // TODO: Add support for types that are lowered to multiple parameters in closures let arguments = parameters.map { - $0.conversion.render(&printer, $0.name) + $0.conversion.render(&printer, $0.parameters.first!.name) } printer.print( @@ -363,7 +580,7 @@ extension JNISwift2JavaGenerator { """ ) - let upcall = "environment!.interface.\(nativeResult.javaType.jniType.callMethodAName)(environment, \(placeholder), methodID$, arguments$)" + let upcall = "environment!.interface.\(nativeResult.javaType.jniCallMethodAName)(environment, \(placeholder), methodID$, arguments$)" let result = nativeResult.conversion.render(&printer, upcall) if nativeResult.javaType.isVoid { @@ -377,8 +594,92 @@ extension JNISwift2JavaGenerator { return printer.finalize() - case .initializeJavaKitWrapper(let wrapperName): - return "\(wrapperName)(javaThis: \(placeholder), environment: environment!)" + case .initializeJavaKitWrapper(let inner, let wrapperName): + let inner = inner.render(&printer, placeholder) + return "\(wrapperName)(javaThis: \(inner), environment: environment!)" + + case .optionalLowering(let valueConversion, let discriminatorName, let valueName): + let value = valueConversion.render(&printer, valueName) + return "\(discriminatorName) == 1 ? \(value) : nil" + + case .optionalChain(let inner): + let inner = inner.render(&printer, placeholder) + return "\(inner)?" + + case .optionalRaisingWidenIntegerType(let inner, let valueType, let combinedSwiftType, let valueSizeInBytes): + let inner = inner.render(&printer, placeholder) + let value = valueType == .boolean ? "$0 ? 1 : 0" : "$0" + let combinedSwiftTypeName = combinedSwiftType.moduleAndName.name + printer.print( + """ + let value$ = \(inner).map { + \(combinedSwiftTypeName)(\(value)) << \(valueSizeInBytes * 8) | \(combinedSwiftTypeName)(1) + } ?? 0 + """ + ) + return "value$" + + case .optionalRaisingIndirectReturn(let inner, let returnType, let discriminatorParameterName, let placeholderValue): + printer.print("let result$: \(returnType.jniTypeName)") + printer.printBraceBlock("if let innerResult$ = \(placeholder)") { printer in + let inner = inner.render(&printer, "innerResult$") + printer.print( + """ + result$ = \(inner) + var flag$ = Int8(1) + environment.interface.SetByteArrayRegion(environment, \(discriminatorParameterName), 0, 1, &flag$) + """ + ) + } + printer.printBraceBlock("else") { printer in + let placeholderValue = placeholderValue.render(&printer, placeholder) + printer.print( + """ + result$ = \(placeholderValue) + var flag$ = Int8(0) + environment.interface.SetByteArrayRegion(environment, \(discriminatorParameterName), 0, 1, &flag$) + """ + ) + } + + return "result$" + + case .method(let inner, let methodName, let arguments): + let inner = inner.render(&printer, placeholder) + let args = arguments.map { name, value in + let value = value.render(&printer, placeholder) + if let name { + return "\(name): \(value)" + } else { + return value + } + } + let argsStr = args.joined(separator: ", ") + return "\(inner).\(methodName)(\(argsStr))" + + case .member(let inner, let member): + let inner = inner.render(&printer, placeholder) + return "\(inner).\(member)" + + case .optionalMap(let inner): + var printer = CodePrinter() + printer.printBraceBlock("\(placeholder).map") { printer in + let inner = inner.render(&printer, "$0") + printer.print("return \(inner)") + } + return printer.finalize() + + case .unwrapOptional(let inner, let name, let fatalErrorMessage): + let unwrappedName = "\(name)_unwrapped$" + let inner = inner.render(&printer, placeholder) + printer.print( + """ + guard let \(unwrappedName) = \(inner) else { + fatalError("\(fatalErrorMessage)") + } + """ + ) + return unwrappedName } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 8b910ffb..ca580e60 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -125,18 +125,20 @@ extension JNISwift2JavaGenerator { } let nativeSignature = translatedDecl.nativeFunctionSignature - var parameters = nativeSignature.parameters + var parameters = nativeSignature.parameters.flatMap(\.parameters) if let selfParameter = nativeSignature.selfParameter { - parameters.append(selfParameter) + parameters += selfParameter.parameters } + parameters += nativeSignature.result.outParameters + printCDecl( &printer, javaMethodName: translatedDecl.nativeFunctionName, parentName: translatedDecl.parentName, - parameters: parameters.map { JavaParameter(name: $0.name, type: $0.javaType) }, - resultType: nativeSignature.result.javaType.jniType + parameters: parameters, + resultType: nativeSignature.result.javaType ) { printer in self.printFunctionDowncall(&printer, decl) } @@ -155,8 +157,9 @@ extension JNISwift2JavaGenerator { // Regular parameters. var arguments = [String]() - for parameter in nativeSignature.parameters { - let lowered = parameter.conversion.render(&printer, parameter.name) + for (idx, parameter) in nativeSignature.parameters.enumerated() { + let javaParameterName = translatedDecl.translatedFunctionSignature.parameters[idx].parameter.name + let lowered = parameter.conversion.render(&printer, javaParameterName) arguments.append(lowered) } @@ -230,7 +233,7 @@ extension JNISwift2JavaGenerator { javaMethodName: String, parentName: String, parameters: [JavaParameter], - resultType: JNIType, + resultType: JavaType, _ body: (inout CodePrinter) -> Void ) { let jniSignature = parameters.reduce(into: "") { signature, parameter in @@ -246,7 +249,7 @@ extension JNISwift2JavaGenerator { + jniSignature.escapedJNIIdentifier let translatedParameters = parameters.map { - "\($0.name): \($0.type.jniType)" + "\($0.name): \($0.type.jniTypeName)" } let thunkParameters = @@ -254,7 +257,7 @@ extension JNISwift2JavaGenerator { "environment: UnsafeMutablePointer!", "thisClass: jclass" ] + translatedParameters - let thunkReturnType = resultType != .void ? " -> \(resultType)" : "" + let thunkReturnType = resultType != .void ? " -> \(resultType.jniTypeName)" : "" // TODO: Think about function overloads printer.printBraceBlock( diff --git a/Sources/JExtractSwiftLib/JNI/JNIType.swift b/Sources/JExtractSwiftLib/JNI/JNIType.swift deleted file mode 100644 index cdedb0a1..00000000 --- a/Sources/JExtractSwiftLib/JNI/JNIType.swift +++ /dev/null @@ -1,98 +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 -// -//===----------------------------------------------------------------------===// - -import JavaTypes - -/// Represents types that are able to be passed over a JNI boundary. -/// -/// - SeeAlso: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html -enum JNIType { - case jboolean - case jfloat - case jdouble - case jbyte - case jchar - case jshort - case jint - case jlong - case void - case jstring - case jclass - case jthrowable - case jobject - case jbooleanArray - case jbyteArray - case jcharArray - case jshortArray - case jintArray - case jlongArray - case jfloatArray - case jdoubleArray - case jobjectArray - - var callMethodAName: String { - switch self { - case .jboolean: "CallBooleanMethodA" - case .jbyte: "CallByteMethodA" - case .jchar: "CallCharMethodA" - case .jshort: "CallShortMethodA" - case .jint: "CallIntMethodA" - case .jlong: "CallLongMethodA" - case .jfloat: "CallFloatMethodA" - case .jdouble: "CallDoubleMethodA" - case .void: "CallVoidMethodA" - case .jobject, .jstring, .jclass, .jthrowable: "CallObjectMethodA" - case .jbooleanArray, .jbyteArray, .jcharArray, .jshortArray, .jintArray, .jlongArray, .jfloatArray, .jdoubleArray, .jobjectArray: "CallObjectMethodA" - } - } -} - -extension JavaType { - var jniType: JNIType { - switch self { - case .boolean: .jboolean - case .byte: .jbyte - case .char: .jchar - case .short: .jshort - case .int: .jint - case .long: .jlong - case .float: .jfloat - case .double: .jdouble - case .void: .void - case .array(.boolean): .jbooleanArray - case .array(.byte): .jbyteArray - case .array(.char): .jcharArray - case .array(.short): .jshortArray - case .array(.int): .jintArray - case .array(.long): .jlongArray - case .array(.float): .jfloatArray - case .array(.double): .jdoubleArray - case .array: .jobjectArray - case .javaLangString: .jstring - case .javaLangClass: .jclass - case .javaLangThrowable: .jthrowable - case .class: .jobject - } - } - - /// Returns whether this type returns `JavaValue` from JavaKit - var implementsJavaValue: Bool { - return switch self { - case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString: - true - default: - false - } - } -} diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index b3d02276..130c333b 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -63,7 +63,8 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | `Foundation.Data`, `any Foundation.DataProtocol` | ✅ | ❌ | | Tuples: `(Int, String)`, `(A, B, C)` | ❌ | ❌ | | Protocols: `protocol`, existential parameters `any Collection` | ❌ | ❌ | -| Optional types: `Int?`, `AnyObject?` | ❌ | ❌ | +| Optional parameters: `func f(i: Int?, class: MyClass?)` | ✅ | ✅ | +| Optional return types: `func f() -> Int?`, `func g() -> MyClass?` | ❌ | ✅ | | Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | | Parameters: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ✅ | | Return values: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ❌ | diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 483d53f5..5b015f89 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -283,7 +283,8 @@ struct JNIClassTests { func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) @@ -331,7 +332,8 @@ struct JNIClassTests { func Java_com_example_swift_MyClass__00024copy__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } let result$ = UnsafeMutablePointer.allocate(capacity: 1) @@ -382,12 +384,14 @@ struct JNIClassTests { func Java_com_example_swift_MyClass__00024isEqual__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, other: jlong, self: jlong) -> jboolean { assert(other != 0, "other memory address was null") let otherBits$ = Int(Int64(fromJNI: other, in: environment!)) - guard let other$ = UnsafeMutablePointer(bitPattern: otherBits$) else { + let other$ = UnsafeMutablePointer(bitPattern: otherBits$) + guard let other$ else { fatalError("other memory address was null in call to \\(#function)!") } assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment!) diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index 47d7e35d..9a388da1 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -61,7 +61,7 @@ struct JNIClosureTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2") - func Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject) { + func Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.emptyClosure(closure: { let class$ = environment!.interface.GetObjectClass(environment, closure) let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "()V")! @@ -113,7 +113,7 @@ struct JNIClosureTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2") - func Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject) { + func Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.closureWithArgumentsAndReturn(closure: { _0, _1 in let class$ = environment!.interface.GetObjectClass(environment, closure) let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "(JZ)J")! diff --git a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift index 0283780a..1f19c8f9 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift @@ -64,8 +64,14 @@ struct JNIJavaKitTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024function__Ljava_lang_Long_2Ljava_lang_Integer_2J") - func Java_com_example_swift_SwiftModule__00024function__Ljava_lang_Long_2Ljava_lang_Integer_2J(environment: UnsafeMutablePointer!, thisClass: jclass, javaLong: jobject, javaInteger: jobject, int: jlong) { - SwiftModule.function(javaLong: JavaLong(javaThis: javaLong, environment: environment!), javaInteger: JavaInteger(javaThis: javaInteger, environment: environment!), int: Int64(fromJNI: int, in: environment!)) + func Java_com_example_swift_SwiftModule__00024function__Ljava_lang_Long_2Ljava_lang_Integer_2J(environment: UnsafeMutablePointer!, thisClass: jclass, javaLong: jobject?, javaInteger: jobject?, int: jlong) { + guard let javaLong_unwrapped$ = javaLong else { + fatalError("javaLong was null in call to \\(#function), but Swift requires non-optional!") + } + guard let javaInteger_unwrapped$ = javaInteger else { + fatalError("javaInteger was null in call to \\(#function), but Swift requires non-optional!") + } + SwiftModule.function(javaLong: JavaLong(javaThis: javaLong_unwrapped$, environment: environment!), javaInteger: JavaInteger(javaThis: javaInteger_unwrapped$, environment: environment!), int: Int64(fromJNI: int, in: environment!)) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index be9cf0ce..198276ba 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -45,6 +45,7 @@ struct JNIModuleTests { import org.swift.swiftkit.core.*; import org.swift.swiftkit.core.util.*; + import java.util.*; import org.swift.swiftkit.core.annotations.*; public final class SwiftModule { @@ -176,7 +177,7 @@ struct JNIModuleTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2") - func Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring) -> jstring { + func Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { return SwiftModule.copy(String(fromJNI: string, in: environment!)).getJNIValue(in: environment!) } """, diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift new file mode 100644 index 00000000..cd04660b --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -0,0 +1,253 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIOptionalTests { + let source = + """ + class MyClass { } + + public func optionalSugar(_ arg: Int64?) -> Int32? + public func optionalExplicit(_ arg: Optional) -> Optional + public func optionalClass(_ arg: MyClass?) -> MyClass? + public func optionalJavaKitClass(_ arg: JavaLong?) + """ + + let classLookupTable = [ + "JavaLong": "java.lang.Long", + ] + + @Test + func optionalSugar_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalSugar(_ arg: Int64?) -> Int32? + * } + */ + public static OptionalInt optionalSugar(OptionalLong arg) { + long combined$ = SwiftModule.$optionalSugar((byte) (arg.isPresent() ? 1 : 0), arg.orElse(0L)); + byte discriminator$ = (byte) (combined$ & 0xFF); + int value$ = (int) (combined$ >> 32); + return discriminator$ == 1 ? OptionalInt.of(value$) : OptionalInt.empty(); + } + """, + """ + private static native long $optionalSugar(byte arg_discriminator, long arg_value); + """ + ] + ) + } + + @Test + func optionalSugar_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024optionalSugar__BJ") + func Java_com_example_swift_SwiftModule__00024optionalSugar__BJ(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jlong) -> jlong { + let value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment!) : nil).map { + Int64($0) << 32 | Int64(1) + } ?? 0 + return value$.getJNIValue(in: environment!) + } + """ + ] + ) + } + + @Test + func optionalExplicit_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalExplicit(_ arg: Optional) -> Optional + * } + */ + public static Optional optionalExplicit(Optional arg) { + byte[] result_discriminator$ = new byte[1]; + java.lang.String result$ = SwiftModule.$optionalExplicit((byte) (arg.isPresent() ? 1 : 0), arg.orElse(null), result_discriminator$); + return (result_discriminator$[0] == 1) ? Optional.of(result$) : Optional.empty(); + } + """, + """ + private static native java.lang.String $optionalExplicit(byte arg_discriminator, java.lang.String arg_value, byte[] result_discriminator$); + """ + ] + ) + } + + @Test + func optionalExplicit_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B") + func Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jstring?, result_discriminator$: jbyteArray?) -> jstring? { + let result$: jstring? + if let innerResult$ = SwiftModule.optionalExplicit(arg_discriminator == 1 ? String(fromJNI: arg_value, in: environment!) : nil) { + result$ = innerResult$.getJNIValue(in: environment!) + var flag$ = Int8(1) + environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:624 + else { + result$ = String.jniPlaceholderValue + var flag$ = Int8(0) + environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:634 + return result$ + } + """ + ] + ) + } + + @Test + func optionalClass_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalClass(_ arg: MyClass?) -> MyClass? + * } + */ + public static Optional optionalClass(Optional arg, SwiftArena swiftArena$) { + byte[] result_discriminator$ = new byte[1]; + long result$ = SwiftModule.$optionalClass(arg.map(MyClass::$memoryAddress).orElse(0L), result_discriminator$); + return (result_discriminator$[0] == 1) ? Optional.of(new MyClass(result$, swiftArena$)) : Optional.empty(); + } + """, + """ + private static native long $optionalClass(long arg, byte[] result_discriminator$); + """ + ] + ) + } + + @Test + func optionalClass_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024optionalClass__J_3B") + func Java_com_example_swift_SwiftModule__00024optionalClass__J_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jlong, result_discriminator$: jbyteArray?) -> jlong { + let argBits$ = Int(Int64(fromJNI: arg, in: environment!)) + let arg$ = UnsafeMutablePointer(bitPattern: argBits$) + let result$: jlong + if let innerResult$ = SwiftModule.optionalClass(arg$?.pointee) { + let _result$ = UnsafeMutablePointer.allocate(capacity: 1) + _result$.initialize(to: innerResult$) + let _resultBits$ = Int64(Int(bitPattern: _result$)) + result$ = _resultBits$.getJNIValue(in: environment!) + var flag$ = Int8(1) + environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:624 + else { + result$ = 0 + var flag$ = Int8(0) + environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:634 + return result$ + } + """ + ] + ) + } + + @Test + func optionalJavaKitClass_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalJavaKitClass(_ arg: JavaLong?) + * } + */ + public static void optionalJavaKitClass(Optional arg) { + SwiftModule.$optionalJavaKitClass(arg.orElse(null)); + } + """, + """ + private static native void $optionalJavaKitClass(java.lang.Long arg); + """ + ] + ) + } + + @Test + func optionalJavaKitClass_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2") + func Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jobject?) { + SwiftModule.optionalJavaKitClass(arg.map { + return JavaLong(javaThis: $0, environment: environment!) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:666 + ) + } + """ + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index 09a8626d..01a2e3c0 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -205,7 +205,8 @@ struct JNIStructTests { func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 9d2fcb22..933e4f08 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -71,7 +71,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.constant.getJNIValue(in: environment!) @@ -134,7 +135,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.mutable.getJNIValue(in: environment!) @@ -145,7 +147,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) @@ -194,7 +197,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.computed.getJNIValue(in: environment!) @@ -243,7 +247,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } do { @@ -311,7 +316,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.getterAndSetter.getJNIValue(in: environment!) @@ -322,7 +328,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) @@ -385,7 +392,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.someBoolean.getJNIValue(in: environment!) @@ -396,7 +404,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) @@ -459,7 +468,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.isBoolean.getJNIValue(in: environment!) @@ -470,7 +480,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment!)