diff --git a/packages/nitrogen/src/syntax/kotlin/KotlinStruct.ts b/packages/nitrogen/src/syntax/kotlin/KotlinStruct.ts index 58a6c05c46..d5e9ba9082 100644 --- a/packages/nitrogen/src/syntax/kotlin/KotlinStruct.ts +++ b/packages/nitrogen/src/syntax/kotlin/KotlinStruct.ts @@ -50,8 +50,10 @@ private fun get${name}_cxx(): ${type} { }) .join('\n\n') - const equalityComparators = structType.properties.map((p) => `Objects.deepEquals(this.${p.escapedName}, other.${p.escapedName})`) - const propertiesList = structType.properties.map((p) => p.escapedName).join(',\n') + const equalityComparators = structType.properties.map( + (property) => `Objects.deepEquals(this.${property.escapedName}, other.${property.escapedName})` + ) + const propertiesList = structType.properties.map((property) => property.escapedName).join(',\n') const code = ` ${createFileMetadataString(`${structType.structName}.kt`)} @@ -118,12 +120,14 @@ data class ${structType.structName}( const returnType = property.getCode('c++', { context: 'return' }) const signatureType = getJniCode(property, { context: 'property' }) const name = capitalizeName(property.escapedName) - const methodResult = convertJniValue(property, 'method(this->self())', 'from-jni') + const methodResult = convertJniValue(property, 'method(this->self(), clazz)', 'from-jni') const fieldResult = convertJniValue(property, 'getFieldValue(field)', 'from-jni') if (!property.isKotlinFieldCompatible) { + // The getter is private and final, so JNI calls it without a virtual lookup. return ` ${returnType} get${name}() const { - static const auto method = javaClassStatic()->getMethod<${signatureType}()>("get${name}_cxx"); + static const auto clazz = javaClassStatic(); + static const auto method = clazz->getNonvirtualMethod<${signatureType}()>("get${name}_cxx"); return ${methodResult}; } `.trim() diff --git a/packages/nitrogen/src/syntax/kotlin/KotlinVariant.ts b/packages/nitrogen/src/syntax/kotlin/KotlinVariant.ts index e634ca9d42..cdaab07c6a 100644 --- a/packages/nitrogen/src/syntax/kotlin/KotlinVariant.ts +++ b/packages/nitrogen/src/syntax/kotlin/KotlinVariant.ts @@ -14,13 +14,27 @@ export function createKotlinVariant(variant: KotlinVariantType): SourceFile[] { const kotlinName = variant.name const namespace = `${jniName}_impl` - const innerClasses = variant.kotlinCases.map(({ label, type }, index) => { + const innerClasses = variant.kotlinCases.map(({ label, publicType, type }, index) => { const innerName = capitalizeName(label) - const valueType = type.getCode('kotlin', { context: 'property' }) - return ` + const valueType = publicType.getCode('kotlin', { context: 'property' }) + const classCode = ` @DoNotStrip data class ${innerName}(@DoNotStrip val value: ${valueType}): ${kotlinName}(${index}) - `.trim() +`.trim() + const isFieldCompatible = type.isKotlinFieldCompatible ?? !type.needsKotlinConversion + if (isFieldCompatible) return classCode + // JNI reads values that Kotlin represents differently, e.g. `Long` for `ULong`, through an accessor. + const bridgeType = type.getCode('kotlin', { context: 'return' }) + const bridgeValue = type.convert('value', { from: 'kotlin', to: 'c++', in: 'kotlin' }) + return ` +${classCode} { + @DoNotStrip + @Suppress("unused", "FunctionName") + private fun getValue_cxx(): ${bridgeType} { + return ${bridgeValue} + } +} +`.trim() }) const packageName = config.getAndroidPackage('java/kotlin') @@ -31,19 +45,14 @@ val is${innerName}: Boolean get() = this is ${innerName} `.trim() }) - const asFunctions = variant.kotlinCases.map(({ label, publicType, type }) => { + const asFunctions = variant.kotlinCases.map(({ label, publicType }) => { const innerName = capitalizeName(label) const optional = new OptionalType(publicType) const optionalType = optional.getCode('kotlin', { context: 'return' }) - const publicValue = type.convert('value', { - from: 'c++', - to: 'kotlin', - in: 'kotlin', - }) return ` fun as${innerName}OrNull(): ${optionalType} { val value = (this as? ${innerName})?.value ?: return null - return ${publicValue} + return value } `.trim() }) @@ -51,26 +60,16 @@ fun as${innerName}OrNull(): ${optionalType} { const type = publicType.getCode('kotlin', { context: 'parameter' }) return `${label}: (${type}) -> R` }) - const matchCases = variant.kotlinCases.map(({ label, type }) => { + const matchCases = variant.kotlinCases.map(({ label }) => { const innerName = capitalizeName(label) - const publicValue = type.convert('value', { - from: 'c++', - to: 'kotlin', - in: 'kotlin', - }) return ` -is ${innerName} -> ${label}(${publicValue}) +is ${innerName} -> ${label}(value) `.trim() }) - const asCases = variant.kotlinCases.map(({ label, type }) => { + const asCases = variant.kotlinCases.map(({ label }) => { const innerName = capitalizeName(label) - const publicValue = type.convert('value', { - from: 'c++', - to: 'kotlin', - in: 'kotlin', - }) return ` -is ${innerName} -> (${publicValue}) as? T +is ${innerName} -> (value) as? T `.trim() }) @@ -78,9 +77,9 @@ is ${innerName} -> (${publicValue}) as? T const innerName = capitalizeName(label) const valueType = publicType.getCode('kotlin', { context: 'parameter' }) const bridgeType = type.getCode('kotlin', { context: 'parameter' }) - const bridgeValue = type.convert('value', { - from: 'kotlin', - to: 'c++', + const publicValue = type.convert('value', { + from: 'c++', + to: 'kotlin', in: 'kotlin', }) // Cases such as two callbacks can erase to the same JVM type, so give each a JVM name. @@ -88,11 +87,11 @@ is ${innerName} -> (${publicValue}) as? T @JvmStatic @JvmName("create${innerName}") @DoNotStrip -fun create(value: ${valueType}): ${kotlinName} = ${innerName}(${bridgeValue}) +fun create(value: ${valueType}): ${kotlinName} = ${innerName}(value) @JvmStatic @DoNotStrip -private fun fromCpp_${i}(value: ${bridgeType}): ${kotlinName} = ${innerName}(value) +private fun fromCpp_${i}(value: ${bridgeType}): ${kotlinName} = ${innerName}(${publicValue}) `.trim() }) @@ -150,7 +149,23 @@ sealed class ${kotlinName} private constructor(@DoNotStrip private val index: In const valueType = type.getCode('c++', { context: 'return' }) const parameterType = type.getCode('c++', { context: 'parameter' }) const createValue = convertJniValue(type, 'value', 'to-jni') - const extractedValue = convertJniValue(type, 'getFieldValue(field)', 'from-jni') + const isFieldCompatible = type.isKotlinFieldCompatible ?? !type.needsKotlinConversion + let getValueBody: string + if (isFieldCompatible) { + const extractedValue = convertJniValue(type, 'getFieldValue(field)', 'from-jni') + getValueBody = ` +static const auto field = javaClassStatic()->getField<${jniField}>("value"); +return ${extractedValue}; +`.trim() + } else { + // The accessor is private and final, so JNI calls it without a virtual lookup. + const extractedValue = convertJniValue(type, 'method(self(), clazz)', 'from-jni') + getValueBody = ` +static const auto clazz = javaClassStatic(); +static const auto method = clazz->getNonvirtualMethod<${jniField}()>("getValue_cxx"); +return ${extractedValue}; +`.trim() + } const declaration = ` class ${innerName} final : public jni::JavaClass<${innerName}, ${jniName}> { public: @@ -168,8 +183,7 @@ jni::local_ref<${jniName}> ${namespace}::${innerName}::create(${parameterType} v } ${namespace}::${innerName}::Value ${namespace}::${innerName}::getValue() const { - static const auto field = javaClassStatic()->getField<${jniField}>("value"); - return ${extractedValue}; + ${indent(getValueBody, ' ')} } `.trim() return { name: innerName, declaration: declaration, implementation: implementation } diff --git a/tests/standalone/nitro-test-external/nitrogen/generated/android/c++/include/NitroTestExternal/private/JOptionalPrimitivesHolder.hpp b/tests/standalone/nitro-test-external/nitrogen/generated/android/c++/include/NitroTestExternal/private/JOptionalPrimitivesHolder.hpp index fa1e76c9c5..9200e1f509 100644 --- a/tests/standalone/nitro-test-external/nitrogen/generated/android/c++/include/NitroTestExternal/private/JOptionalPrimitivesHolder.hpp +++ b/tests/standalone/nitro-test-external/nitrogen/generated/android/c++/include/NitroTestExternal/private/JOptionalPrimitivesHolder.hpp @@ -40,8 +40,9 @@ namespace margelo::nitro::test::external { } jni::local_ref> getOptionalUInt64() const { - static const auto method = javaClassStatic()->getMethod()>("getOptionalUInt64_cxx"); - return method(this->self()); + static const auto clazz = javaClassStatic(); + static const auto method = clazz->getNonvirtualMethod()>("getOptionalUInt64_cxx"); + return method(this->self(), clazz); } jni::local_ref> getOptionalInt64() const { diff --git a/tests/standalone/nitro-test-external/nitrogen/generated/android/c++/include/NitroTestExternal/private/JSharedCallbackStruct.hpp b/tests/standalone/nitro-test-external/nitrogen/generated/android/c++/include/NitroTestExternal/private/JSharedCallbackStruct.hpp index 9df1c867ff..27b6b09b7c 100644 --- a/tests/standalone/nitro-test-external/nitrogen/generated/android/c++/include/NitroTestExternal/private/JSharedCallbackStruct.hpp +++ b/tests/standalone/nitro-test-external/nitrogen/generated/android/c++/include/NitroTestExternal/private/JSharedCallbackStruct.hpp @@ -34,8 +34,9 @@ namespace margelo::nitro::test::external { } jni::local_ref getOnChanged() const { - static const auto method = javaClassStatic()->getMethod("getOnChanged_cxx"); - return method(this->self()); + static const auto clazz = javaClassStatic(); + static const auto method = clazz->getNonvirtualMethod("getOnChanged_cxx"); + return method(this->self(), clazz); } }; diff --git a/tests/standalone/nitro-test/nitrogen/generated/android/c++/JCoreTypesVariant.cpp b/tests/standalone/nitro-test/nitrogen/generated/android/c++/JCoreTypesVariant.cpp index 7f40ddd4af..a2025a3088 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/android/c++/JCoreTypesVariant.cpp +++ b/tests/standalone/nitro-test/nitrogen/generated/android/c++/JCoreTypesVariant.cpp @@ -44,8 +44,9 @@ namespace margelo::nitro::test { } JCoreTypesVariant_impl::Second::Value JCoreTypesVariant_impl::Second::getValue() const { - static const auto field = javaClassStatic()->getField("value"); - return getFieldValue(field); + static const auto clazz = javaClassStatic(); + static const auto method = clazz->getNonvirtualMethod("getValue_cxx"); + return method(self(), clazz); } jni::local_ref JCoreTypesVariant_impl::Third::create(jni::alias_ref value) { diff --git a/tests/standalone/nitro-test/nitrogen/generated/android/c++/JVariant_Callback_______Unit__Double.cpp b/tests/standalone/nitro-test/nitrogen/generated/android/c++/JVariant_Callback_______Unit__Double.cpp index 96bc91ff2c..a181a9f21a 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/android/c++/JVariant_Callback_______Unit__Double.cpp +++ b/tests/standalone/nitro-test/nitrogen/generated/android/c++/JVariant_Callback_______Unit__Double.cpp @@ -24,8 +24,9 @@ namespace margelo::nitro::test { } JVariant_Callback_______Unit__Double_impl::First::Value JVariant_Callback_______Unit__Double_impl::First::getValue() const { - static const auto field = javaClassStatic()->getField("value"); - return getFieldValue(field); + static const auto clazz = javaClassStatic(); + static const auto method = clazz->getNonvirtualMethod("getValue_cxx"); + return method(self(), clazz); } jni::local_ref JVariant_Callback_______Unit__Double_impl::Second::create(jdouble value) { diff --git a/tests/standalone/nitro-test/nitrogen/generated/android/c++/include/NitroTest/private/JJsStyleStruct.hpp b/tests/standalone/nitro-test/nitrogen/generated/android/c++/include/NitroTest/private/JJsStyleStruct.hpp index c432b08cdf..c59a2c34ff 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/android/c++/include/NitroTest/private/JJsStyleStruct.hpp +++ b/tests/standalone/nitro-test/nitrogen/generated/android/c++/include/NitroTest/private/JJsStyleStruct.hpp @@ -34,8 +34,9 @@ namespace margelo::nitro::test { } jni::local_ref getOnChanged() const { - static const auto method = javaClassStatic()->getMethod("getOnChanged_cxx"); - return method(this->self()); + static const auto clazz = javaClassStatic(); + static const auto method = clazz->getNonvirtualMethod("getOnChanged_cxx"); + return method(this->self(), clazz); } }; diff --git a/tests/standalone/nitro-test/nitrogen/generated/android/c++/include/NitroTest/private/JUnsignedValues.hpp b/tests/standalone/nitro-test/nitrogen/generated/android/c++/include/NitroTest/private/JUnsignedValues.hpp index 8dc1b465f0..c930ca76bc 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/android/c++/include/NitroTest/private/JUnsignedValues.hpp +++ b/tests/standalone/nitro-test/nitrogen/generated/android/c++/include/NitroTest/private/JUnsignedValues.hpp @@ -44,28 +44,33 @@ namespace margelo::nitro::test { } jni::local_ref> getOptionalUnsigned() const { - static const auto method = javaClassStatic()->getMethod()>("getOptionalUnsigned_cxx"); - return method(this->self()); + static const auto clazz = javaClassStatic(); + static const auto method = clazz->getNonvirtualMethod()>("getOptionalUnsigned_cxx"); + return method(this->self(), clazz); } jni::local_ref getValues() const { - static const auto method = javaClassStatic()->getMethod("getValues_cxx"); - return method(this->self()); + static const auto clazz = javaClassStatic(); + static const auto method = clazz->getNonvirtualMethod("getValues_cxx"); + return method(this->self(), clazz); } jni::local_ref> getNested() const { - static const auto method = javaClassStatic()->getMethod()>("getNested_cxx"); - return method(this->self()); + static const auto clazz = javaClassStatic(); + static const auto method = clazz->getNonvirtualMethod()>("getNested_cxx"); + return method(this->self(), clazz); } jni::local_ref>> getRecord() const { - static const auto method = javaClassStatic()->getMethod>()>("getRecord_cxx"); - return method(this->self()); + static const auto clazz = javaClassStatic(); + static const auto method = clazz->getNonvirtualMethod>()>("getRecord_cxx"); + return method(this->self(), clazz); } jni::local_ref> getCallbacks() const { - static const auto method = javaClassStatic()->getMethod()>("getCallbacks_cxx"); - return method(this->self()); + static const auto clazz = javaClassStatic(); + static const auto method = clazz->getNonvirtualMethod()>("getCallbacks_cxx"); + return method(this->self(), clazz); } }; diff --git a/tests/standalone/nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/CoreTypesVariant.kt b/tests/standalone/nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/CoreTypesVariant.kt index 1ac8e98a70..b4201bf7ea 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/CoreTypesVariant.kt +++ b/tests/standalone/nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/CoreTypesVariant.kt @@ -22,7 +22,13 @@ sealed class CoreTypesVariant private constructor(@DoNotStrip private val index: @DoNotStrip data class First(@DoNotStrip val value: ArrayBuffer): CoreTypesVariant(0) @DoNotStrip - data class Second(@DoNotStrip val value: Callback_void_double): CoreTypesVariant(1) + data class Second(@DoNotStrip val value: Callback<(value: Double) -> Unit>): CoreTypesVariant(1) { + @DoNotStrip + @Suppress("unused", "FunctionName") + private fun getValue_cxx(): Callback_void_double { + return Callback_void_double.fromKotlin(value) + } + } @DoNotStrip data class Third(@DoNotStrip val value: WrappedJsStruct): CoreTypesVariant(2) @DoNotStrip @@ -96,7 +102,7 @@ sealed class CoreTypesVariant private constructor(@DoNotStrip private val index: @JvmStatic @JvmName("createSecond") @DoNotStrip - fun create(value: Callback<(value: Double) -> Unit>): CoreTypesVariant = Second(Callback_void_double.fromKotlin(value)) + fun create(value: Callback<(value: Double) -> Unit>): CoreTypesVariant = Second(value) @JvmStatic @DoNotStrip diff --git a/tests/standalone/nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/Variant_Callback_______Unit__Double.kt b/tests/standalone/nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/Variant_Callback_______Unit__Double.kt index cfc4ae5b66..aef4236b8c 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/Variant_Callback_______Unit__Double.kt +++ b/tests/standalone/nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/Variant_Callback_______Unit__Double.kt @@ -17,7 +17,13 @@ import com.margelo.nitro.core.Callback @DoNotStrip sealed class Variant_Callback_______Unit__Double private constructor(@DoNotStrip private val index: Int) { @DoNotStrip - data class First(@DoNotStrip val value: Callback_void): Variant_Callback_______Unit__Double(0) + data class First(@DoNotStrip val value: Callback<() -> Unit>): Variant_Callback_______Unit__Double(0) { + @DoNotStrip + @Suppress("unused", "FunctionName") + private fun getValue_cxx(): Callback_void { + return Callback_void.fromKotlin(value) + } + } @DoNotStrip data class Second(@DoNotStrip val value: Double): Variant_Callback_______Unit__Double(1) @@ -55,7 +61,7 @@ sealed class Variant_Callback_______Unit__Double private constructor(@DoNotStrip @JvmStatic @JvmName("createFirst") @DoNotStrip - fun create(value: Callback<() -> Unit>): Variant_Callback_______Unit__Double = First(Callback_void.fromKotlin(value)) + fun create(value: Callback<() -> Unit>): Variant_Callback_______Unit__Double = First(value) @JvmStatic @DoNotStrip