Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/nitrogen/src/syntax/kotlin/KotlinStruct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,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()
Expand Down
80 changes: 47 additions & 33 deletions packages/nitrogen/src/syntax/kotlin/KotlinVariant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -31,68 +45,53 @@ 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()
})
const matchParameters = variant.kotlinCases.map(({ label, publicType }) => {
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()
})

const createFunctions = variant.kotlinCases.map(({ label, publicType, type }, i) => {
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.
return `
@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()
})

Expand Down Expand Up @@ -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:
Expand All @@ -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 }
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,21 @@ class HybridTestObjectKotlin : HybridTestObjectSwiftKotlinSpec() {
return callback.callSync(array)
}

override fun bounceUnsignedVariant(variant: Variant_ULong_String): Variant_ULong_String {
// Rebuild the case so it goes through the public ULong constructor.
return when (variant) {
is Variant_ULong_String.First -> Variant_ULong_String.First(variant.value)
is Variant_ULong_String.Second -> Variant_ULong_String.Second(variant.value)
}
}

override fun bounceUnsignedArrayVariant(variant: Variant_ULongArray_String): Variant_ULongArray_String {
return variant.match(
{ array -> Variant_ULongArray_String.create(array) },
{ string -> Variant_ULongArray_String.create(string) },
)
}

override fun bounceHybridObjects(array: Array<HybridChildSpec>): Array<HybridChildSpec> {
return array
}
Expand Down
8 changes: 8 additions & 0 deletions tests/standalone/nitro-test/apple/HybridTestObjectSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,14 @@ class HybridTestObjectSwift: HybridTestObjectSwiftKotlinSpec {
return try callback.callSync(array)
}

func bounceUnsignedVariant(variant: Variant_UInt64_String) throws -> Variant_UInt64_String {
return variant
}

func bounceUnsignedArrayVariant(variant: Variant__UInt64__String) throws -> Variant__UInt64__String {
return variant
}

func bounceHybridObjects(array: [(any HybridChildSpec)]) throws -> [(any HybridChildSpec)] {
return array
}
Expand Down
9 changes: 9 additions & 0 deletions tests/standalone/nitro-test/cpp/HybridTestObjectCpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,15 @@ HybridTestObjectCpp::callUnsignedArrayCallback(const std::vector<uint64_t>& arra
return callback.callSync(array);
}

std::variant<uint64_t, std::string> HybridTestObjectCpp::bounceUnsignedVariant(const std::variant<uint64_t, std::string>& variant) {
return variant;
}

std::variant<std::vector<uint64_t>, std::string>
HybridTestObjectCpp::bounceUnsignedArrayVariant(const std::variant<std::vector<uint64_t>, std::string>& variant) {
return variant;
}

std::vector<HybridChildSpecCxx> HybridTestObjectCpp::bounceHybridObjects(const std::vector<HybridChildSpecCxx>& array) {
return array;
}
Expand Down
3 changes: 3 additions & 0 deletions tests/standalone/nitro-test/cpp/HybridTestObjectCpp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ class HybridTestObjectCpp : public HybridTestObjectCppSpec {
Promise<std::vector<uint64_t>> bounceUnsignedArrayAsync(const std::vector<uint64_t>& array) override;
std::vector<uint64_t> callUnsignedArrayCallback(const std::vector<uint64_t>& array,
const Callback<std::vector<uint64_t>(const std::vector<uint64_t>&)>& callback) override;
std::variant<uint64_t, std::string> bounceUnsignedVariant(const std::variant<uint64_t, std::string>& variant) override;
std::variant<std::vector<uint64_t>, std::string>
bounceUnsignedArrayVariant(const std::variant<std::vector<uint64_t>, std::string>& variant) override;

std::vector<HybridChildSpecCxx> bounceHybridObjects(const std::vector<HybridChildSpecCxx>& array) override;
std::vector<Callback<void()>> bounceFunctions(const std::vector<Callback<void()>>& functions) override;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading