From 58909550e726b24cd64c8d46009e4aecbb94503f Mon Sep 17 00:00:00 2001 From: nerzh Date: Thu, 14 May 2026 20:38:33 +0200 Subject: [PATCH] fix optional Int JNI conversion --- ...wift2JavaGenerator+NativeTranslation.swift | 29 ++++- .../JNI/JNIIntConversionChecksTests.swift | 108 ++++++++++++++++++ 2 files changed, 133 insertions(+), 4 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 5335b81b1..e6816f925 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -485,18 +485,30 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftType) } + let indirectStepType = JNIJavaTypeTranslator.indirectConversionStepSwiftType( + for: knownType, + from: knownTypes + ) + let indirectCheck = JNIJavaTypeTranslator.checkStep(for: knownType, from: knownTypes) + let valueConversion: NativeSwiftConversionStep = + if indirectStepType != nil { + .labelessAssignmentOfVariable(.constant(parameterName), swiftType: swiftType) + } else { + .initFromJNI(.placeholder, swiftType: swiftType) + } + return NativeParameter( parameters: [ JavaParameter(name: discriminatorName, type: .byte), JavaParameter(name: valueName, type: javaType), ], conversion: .optionalLowering( - .initFromJNI(.placeholder, swiftType: swiftType), + valueConversion, discriminatorName: discriminatorName, valueName: valueName ), - indirectConversion: nil, - conversionCheck: nil + indirectConversion: indirectStepType.flatMap { .initFromJNI(.constant(valueName), swiftType: $0) }, + conversionCheck: indirectCheck ) } } @@ -568,11 +580,20 @@ extension JNISwift2JavaGenerator { ) } else { // Use indirect byte array to store discriminator + let valueConversion: NativeSwiftConversionStep = + if let indirectReturnType = JNIJavaTypeTranslator.indirectConversionStepSwiftType( + for: knownType, + from: knownTypes + ) { + .getJNIValue(.labelessInitializer(.placeholder, swiftType: indirectReturnType)) + } else { + .getJNIValue(.placeholder) + } return NativeResult( javaType: javaType, conversion: .optionalRaisingIndirectReturn( - .getJNIValue(.placeholder), + valueConversion, resultName: "\(resultName)$", returnType: javaType, discriminatorParameterName: discriminatorName diff --git a/Tests/JExtractSwiftTests/JNI/JNIIntConversionChecksTests.swift b/Tests/JExtractSwiftTests/JNI/JNIIntConversionChecksTests.swift index f92453498..a156c8680 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIIntConversionChecksTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIIntConversionChecksTests.swift @@ -36,6 +36,24 @@ struct JNIIntConversionChecksTests { } } """ + private let optionalSignedSource = """ + public struct MyStruct { + public var optionalInt: Int? = nil + + public init(optionalInt: Int?) { + self.optionalInt = optionalInt + } + } + """ + private let optionalUnsignedSource = """ + public struct MyStruct { + public var optionalUInt: UInt? = nil + + public init(optionalUInt: UInt?) { + self.optionalUInt = optionalUInt + } + } + """ private let signedFuncSource = """ public struct MyStruct { public func dummyFunc(arg: Int) -> Int { @@ -109,6 +127,96 @@ struct JNIIntConversionChecksTests { ) } + @Test func generatesOptionalInitWithSignedCheck() throws { + try assertOutput( + input: optionalSignedSource, + .jni, + .swift, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct__00024init__BJ") + public func Java_com_example_swift_MyStruct__00024init__BJ(environment: UnsafeMutablePointer!, thisClass: jclass, optionalInt_discriminator: jbyte, optionalInt_value: jlong) -> jlong { + let optionalInt$indirect = Int64(fromJNI: optionalInt_value, in: environment) + #if _pointerBitWidth(_32) + guard optionalInt$indirect >= Int32.min && optionalInt$indirect <= Int32.max else { + environment.throwJavaException(javaException: .integerOverflow) + return 0 + """, + """ + #endif + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyStruct.init(optionalInt: optionalInt_discriminator == 1 ? Int(optionalInt$indirect) : nil)) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNILocalRefValue(in: environment) + """, + ] + ) + } + + @Test func generatesOptionalInitWithUnsignedCheck() throws { + try assertOutput( + input: optionalUnsignedSource, + .jni, + .swift, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct__00024init__BJ") + public func Java_com_example_swift_MyStruct__00024init__BJ(environment: UnsafeMutablePointer!, thisClass: jclass, optionalUInt_discriminator: jbyte, optionalUInt_value: jlong) -> jlong { + let optionalUInt$indirect = UInt64(fromJNI: optionalUInt_value, in: environment) + #if _pointerBitWidth(_32) + guard optionalUInt$indirect >= UInt32.min && optionalUInt$indirect <= UInt32.max else { + environment.throwJavaException(javaException: .integerOverflow) + return 0 + """, + """ + #endif + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyStruct.init(optionalUInt: optionalUInt_discriminator == 1 ? UInt(optionalUInt$indirect) : nil)) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNILocalRefValue(in: environment) + """, + ] + ) + } + + @Test func generatesOptionalSignedGetterWithoutCheck() throws { + try assertOutput( + input: optionalSignedSource, + .jni, + .swift, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct__00024getOptionalInt__J_3B") + public func Java_com_example_swift_MyStruct__00024getOptionalInt__J_3B(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong, result_discriminator$: jbyteArray?) -> jlong { + """, + """ + let result$: jlong + if let innerResult$ = selfPointer$.pointee.optionalInt { + result$ = Int64(innerResult$).getJNIValue(in: environment) + """, + ] + ) + } + + @Test func generatesOptionalUnsignedGetterWithoutCheck() throws { + try assertOutput( + input: optionalUnsignedSource, + .jni, + .swift, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct__00024getOptionalUInt__J_3B") + public func Java_com_example_swift_MyStruct__00024getOptionalUInt__J_3B(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong, result_discriminator$: jbyteArray?) -> jlong { + """, + """ + let result$: jlong + if let innerResult$ = selfPointer$.pointee.optionalUInt { + result$ = UInt64(innerResult$).getJNIValue(in: environment) + """, + ] + ) + } + @Test func generatesUnsignedSetterWithCheck() throws { try assertOutput( input: unsignedSource,