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
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,33 @@ struct JNIConverter<jni::global_ref<JArrayUInt64>, jni::local_ref<JULongArray>>
}
};

template <>
struct JNIConverter<std::vector<std::vector<uint64_t>>, jni::local_ref<JULongArrays>> {
using JavaArray = jni::local_ref<JArrayUInt64>;

static jni::local_ref<JULongArrays> toJNI(const std::vector<std::vector<uint64_t>>& value) {
const jsize size = static_cast<jsize>(value.size());
auto arrays = JULongBox::newArrays(size);
for (jsize i = 0; i < size; ++i) {
auto array = JNIConverter<std::vector<uint64_t>, JavaArray>::toJNI(value[i]);
auto box = JULongBox::boxArray(array);
arrays->setElement(i, *box);
}
return arrays;
}
static std::vector<std::vector<uint64_t>> fromJNI(jni::alias_ref<JULongArrays> value) {
const jsize size = static_cast<jsize>(value->size());
std::vector<std::vector<uint64_t>> result;
result.reserve(size);
for (jsize i = 0; i < size; ++i) {
auto box = jni::static_ref_cast<JULongArray>(value->getElement(i));
auto array = JULongBox::unboxArray(box);
result.push_back(JNIConverter<std::vector<uint64_t>, JavaArray>::fromJNI(array));
}
return result;
}
};

template <typename CppElement, typename JavaElement>
struct JNIConverter<std::vector<CppElement>, jni::local_ref<jni::JArrayClass<JavaElement>>> {
using JavaArray = jni::JArrayClass<JavaElement>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,31 @@ struct JSIConverter<jni::local_ref<JULongArray>> {
}
};

template <>
struct JSIConverter<jni::local_ref<JULongArrays>> {
using JavaArray = jni::local_ref<JArrayUInt64>;

static jni::local_ref<JULongArrays> fromJSI(jsi::Runtime& runtime, const jsi::Value& value) {
auto array = value.asObject(runtime).asArray(runtime);
const jsize size = static_cast<jsize>(array.size(runtime));
auto result = JULongBox::newArrays(size);
for (jsize i = 0; i < size; ++i) {
auto element = JSIConverter<JavaArray>::fromJSI(runtime, array.getValueAtIndex(runtime, i));
auto box = JULongBox::boxArray(element);
result->setElement(i, *box);
}
return result;
}
static jsi::Array toJSI(jsi::Runtime& runtime, jni::alias_ref<JULongArrays> value) {
return createJSIArray(runtime, value->size(), [&](size_t i) {
auto box = jni::static_ref_cast<JULongArray>(value->getElement(i));
return JSIConverter<JavaArray>::toJSI(runtime, JULongBox::unboxArray(box));
});
}
static bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
return canConvertJSIArray(runtime, value,
[&](const jsi::Value& element) { return JSIConverter<JavaArray>::canConvert(runtime, element); });
}
};

} // namespace margelo::nitro
13 changes: 13 additions & 0 deletions packages/nitro-core/native/android/src/main/cpp/core/JUInt64.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ struct JULongArray : jni::JavaClass<JULongArray> {
static constexpr auto kJavaDescriptor = "Ljava/lang/Object;";
};

/**
* A Kotlin Array<ULongArray>, whose JVM class names kotlin.ULongArray. JNI only sees an Object[],
* creates it with JULongBox, and creates or reads each element's ULongArray box itself.
*/
struct JULongArrays : jni::JavaClass<JULongArrays, jni::JArrayClass<jni::JObject>> {
static constexpr const char* kJavaDescriptor = nullptr;
};

/**
* Creates and reads JULong and JULongArray boxes without calling into Kotlin. nitro-core's ULongBox
* gives JNI their classes and value fields once, since JNI cannot name them.
Expand Down Expand Up @@ -66,6 +74,11 @@ struct JULongBox : jni::JavaClass<JULongBox> {
jobject array = jni::Environment::current()->GetObjectField(value.get(), getULongArrayLayout().field);
return jni::adopt_local(static_cast<JArrayUInt64::javaobject>(array));
}
static jni::local_ref<JULongArrays> newArrays(jsize size) {
jobjectArray arrays = jni::Environment::current()->NewObjectArray(size, getULongArrayLayout().clazz, nullptr);
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
return jni::adopt_local(static_cast<JULongArrays::javaobject>(arrays));
}

private:
struct Layout {
Expand Down
5 changes: 2 additions & 3 deletions packages/nitrogen/src/syntax/kotlin/KotlinStruct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,9 @@ private fun get${name}_cxx(): ${type} {
})
.join('\n\n')

// Values whose boxes compare by identity, such as `ULongArray`, compare by their JNI representation.
// Values whose boxes compare by identity, such as `ULongArray`, compare by their contents.
const getEqualityValue = (property: KotlinNamedType, value: string): string => {
if (property.isKotlinEqualityCompatible) return value
return property.convert(value, { from: 'kotlin', to: 'c++', in: 'kotlin' })
return property.getKotlinEqualityValue(value) ?? value
}
const equalityComparators = structType.properties.map((property) => {
const value = getEqualityValue(property, `this.${property.escapedName}`)
Expand Down
39 changes: 34 additions & 5 deletions packages/nitrogen/src/syntax/kotlin/types/KotlinArrayType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,31 @@ export class KotlinArrayType extends ArrayType implements KotlinType {
}

get needsKotlinConversion(): boolean {
if (this.isULongArray && this.erasedToObject) return false
if (this.erasedToObject && (this.isULongArray || this.isULongArrays)) return false
return this.itemType.needsKotlinConversion
}
/** `ULongArray` is a value class, so its field holds the `long[]` JNI uses. */
get isKotlinFieldCompatible(): boolean {
return this.isULongArray || !this.needsKotlinConversion
}
/** `ULongArray` boxes compare by identity, so compare their `long[]`s instead. */
get isKotlinEqualityCompatible(): boolean {
return !this.isULongArray && (this.itemType.isKotlinEqualityCompatible ?? true)
/** `ULongArray` boxes compare by identity, so structs compare their `long[]`s instead. */
getKotlinEqualityValue(value: string): string | undefined {
if (this.isULongArray) return `${value}.asLongArray()`
const item = this.itemType.getKotlinEqualityValue?.('__item')
if (item == null) return undefined
const sourceCode = this.type.itemType.getCode('kotlin', { context: 'type-argument' })
const resultCode = this.itemType.getCode('kotlin', { context: 'type-argument' })
return `
${value}.mapToArray<${sourceCode}, ${resultCode}> { __item -> ${item} }
`.trim()
}
private get isULongArray(): boolean {
return this.itemType.kind === 'uint64'
}
/** `Array<ULongArray>`, which JNI creates and reads itself since its JVM class names `kotlin.ULongArray`. */
private get isULongArrays(): boolean {
return this.itemType instanceof KotlinArrayType && this.itemType.isULongArray
}
convert(value: string, conversion: KotlinConversion): string {
switch (conversion.in) {
case 'kotlin': {
Expand All @@ -51,6 +62,12 @@ export class KotlinArrayType extends ArrayType implements KotlinType {
// Both reinterpret the same array without copying it.
return conversion.from === 'kotlin' ? `${value}.asLongArray()` : `${value}.asULongArray()`
}
if (this.isULongArrays) {
// JNI creates a `ULongArray[]` and only declares it as an `Object[]`, so the cast always holds.
if (conversion.from === 'kotlin') return value
const publicType = this.type.getCode('kotlin', { context: 'type-argument' })
return `(@Suppress("UNCHECKED_CAST") (${value} as ${publicType}))`
}
const item = this.itemType.convert('__item', conversion)
const sourceType = conversion.from === 'c++' ? this.itemType : this.type.itemType
const resultType = conversion.from === 'c++' ? this.type.itemType : this.itemType
Expand Down Expand Up @@ -92,6 +109,10 @@ ${value}.mapToArray<${sourceCode}, ${resultCode}> { __item -> ${item} }
jniType = this.erasedToObject ? 'JULongArray' : 'JArrayUInt64'
break
default: {
if (this.isULongArrays) {
jniType = 'JULongArrays'
break
}
const item = this.itemType.getCode(language, { ...options, context: 'type-argument' })
jniType = `jni::JArrayClass<${item}>`
break
Expand All @@ -110,6 +131,11 @@ ${value}.mapToArray<${sourceCode}, ${resultCode}> { __item -> ${item} }
case 'uint64':
return this.erasedToObject ? 'ULongArray' : 'LongArray'
default: {
if (this.isULongArrays) {
// JNI only declares it as an `Object[]`, see `JULongArrays`.
if (this.erasedToObject) return this.type.getCode(language, options)
return 'Array<*>'
}
const item = this.itemType.getCode(language, { ...options, context: 'type-argument' })
return `Array<${item}>`
}
Expand All @@ -132,7 +158,10 @@ ${value}.mapToArray<${sourceCode}, ${resultCode}> { __item -> ${item} }
jsiConverterHeader = 'NitroModules/JSIConverter+JPrimitiveArray.hpp'
break
default:
jsiConverterHeader = 'NitroModules/JSIConverter+JArrayClass.hpp'
// `JULongArrays` converts its `JArrayUInt64` elements.
jsiConverterHeader = this.isULongArrays
? 'NitroModules/JSIConverter+JPrimitiveArray.hpp'
: 'NitroModules/JSIConverter+JArrayClass.hpp'
break
}
imports.push(
Expand Down
4 changes: 2 additions & 2 deletions packages/nitrogen/src/syntax/kotlin/types/KotlinNamedType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export class KotlinNamedType extends NamedWrappingType<KotlinType> implements Ko
get isKotlinFieldCompatible(): boolean {
return this.type.isKotlinFieldCompatible ?? !this.type.needsKotlinConversion
}
get isKotlinEqualityCompatible(): boolean {
return this.type.isKotlinEqualityCompatible ?? true
getKotlinEqualityValue(value: string): string | undefined {
return this.type.getKotlinEqualityValue?.(value)
}

get needsKotlinConversion(): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ export class KotlinOptionalType extends OptionalType implements KotlinType {
return this.wrappingType.jniValueKind
}

get isKotlinEqualityCompatible(): boolean {
return this.wrappingType.isKotlinEqualityCompatible ?? true
getKotlinEqualityValue(value: string): string | undefined {
const wrappedValue = this.wrappingType.getKotlinEqualityValue?.('it')
if (wrappedValue == null) return undefined
return `${value}?.let { ${wrappedValue} }`
}

getCppConverter(cppType: Type): KotlinCppConverter {
Expand Down
6 changes: 3 additions & 3 deletions packages/nitrogen/src/syntax/kotlin/types/KotlinType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ export interface KotlinType extends Type {
/** Whether a generated Kotlin backing field already has this JNI representation. */
readonly isKotlinFieldCompatible?: boolean
/**
* Whether `Objects.deepEquals` and `contentDeepHashCode` compare the public Kotlin value
* by its contents. Otherwise, generated structs compare its JNI representation.
* A Kotlin expression that `Objects.deepEquals` and `contentDeepHashCode` compare by contents,
* for public values whose boxes compare by identity, e.g. `ULongArray`. Otherwise `undefined`.
*/
readonly isKotlinEqualityCompatible?: boolean
getKotlinEqualityValue?(value: string): string | undefined
/** Overrides a native value's printer only when its JVM signature differs. */
getJniCode?(options: GetKotlinCodeOptions): string
/** Adapts a native scalar at the JNI boundary when its JVM ABI differs. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,19 @@ class HybridTestObjectKotlin : HybridTestObjectSwiftKotlinSpec() {
return callback.callAsync(array)
}

override fun bounceNestedUnsignedArrayAsync(array: Array<ULongArray>): Promise<Array<ULongArray>> {
return Promise.async {
return@async array
}
}

override fun callNestedUnsignedArrayCallback(
array: Array<ULongArray>,
callback: Callback<(array: Array<ULongArray>) -> Array<ULongArray>>,
): Array<ULongArray> {
return callback.callSync(array)
}

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

func bounceNestedUnsignedArrayAsync(array: [[UInt64]]) throws -> any Promise<[[UInt64]]> {
return .async {
return array
}
}

func callNestedUnsignedArrayCallback(
array: [[UInt64]], callback: any Callback<[[UInt64]], [[UInt64]]>
) throws -> [[UInt64]] {
return try callback.callSync(array)
}

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

Promise<std::vector<std::vector<uint64_t>>>
HybridTestObjectCpp::bounceNestedUnsignedArrayAsync(const std::vector<std::vector<uint64_t>>& array) {
return Promise<std::vector<std::vector<uint64_t>>>::async([=]() { return array; });
}

std::vector<std::vector<uint64_t>> HybridTestObjectCpp::callNestedUnsignedArrayCallback(
const std::vector<std::vector<uint64_t>>& array,
const Callback<std::vector<std::vector<uint64_t>>(const std::vector<std::vector<uint64_t>>&)>& callback) {
return callback.callSync(array);
}

std::vector<HybridChildSpecCxx> HybridTestObjectCpp::bounceHybridObjects(const std::vector<HybridChildSpecCxx>& array) {
return array;
}
Expand Down
4 changes: 4 additions & 0 deletions tests/standalone/nitro-test/cpp/HybridTestObjectCpp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ class HybridTestObjectCpp : public HybridTestObjectCppSpec {
Promise<std::vector<uint64_t>>
callUnsignedArrayCallbackAsync(const std::vector<uint64_t>& array,
const Callback<std::vector<uint64_t>(const std::vector<uint64_t>&)>& callback) override;
Promise<std::vector<std::vector<uint64_t>>> bounceNestedUnsignedArrayAsync(const std::vector<std::vector<uint64_t>>& array) override;
std::vector<std::vector<uint64_t>> callNestedUnsignedArrayCallback(
const std::vector<std::vector<uint64_t>>& array,
const Callback<std::vector<std::vector<uint64_t>>(const std::vector<std::vector<uint64_t>>&)>& callback) 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.

Loading
Loading