Skip to content

Commit

Permalink
feat: New Callback implementation that extends () -> T in Kotlin (f…
Browse files Browse the repository at this point in the history
…aster!) (#470)

* feat: Add `optionalCallback` property test

* fix: Remove unneeded braces for Swift functions

* fix: Implement HybridTestObject

* Update Podfile.lock

* feat: Implement `getComplexCallback(): T`)

* feat: Add actual test

* Update HybridTestObjectKotlin.kt

* lint

* it builds like this

* suppress

* fix: Use `_cxx` suffix and fix missing struct constructors

* fix special handling

* fix: DoNotStrip only if called from C++

* fix: Fix warns

* fix: Use `?.let`

* fix: Fix downcasting WOOHOOO

* fix: Fix upcasting now for functions

* Revert the `try`/`catch` patch changes again

* fix: `fromCpp()` should return the interface instead

* Comments

* fix: Also annotate primary constructor
  • Loading branch information
mrousavy authored Jan 9, 2025
1 parent 96e7716 commit 07d96a0
Show file tree
Hide file tree
Showing 81 changed files with 1,471 additions and 474 deletions.
6 changes: 3 additions & 3 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1888,11 +1888,11 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native/ReactCommon/yoga"

SPEC CHECKSUMS:
boost: 1dca942403ed9342f98334bf4c3621f011aa7946
DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
FBLazyVector: 1bf99bb46c6af9a2712592e707347315f23947aa
fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8
hermes-engine: 06a9c6900587420b90accc394199527c64259db4
NitroImage: b49db0212f1b50aa976b8cdd97072df904b29102
NitroModules: ff8680342cb7008646d8160ff9b30ba74c681c11
Expand Down
19 changes: 19 additions & 0 deletions example/src/getTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,20 @@ export function getTests(
.didReturn(typeof OldEnum.SECOND)
.equals(OldEnum.SECOND)
),
createTest('set optionalCallback, then undefined', () =>
it(() => {
testObject.optionalCallback = () => {}
testObject.optionalCallback = undefined
}).didNotThrow()
),
createTest('get optionalCallback (== self)', () =>
it(() => {
testObject.optionalCallback = () => {}
return testObject.optionalCallback
})
.didNotThrow()
.didReturn('function')
),

// Test basic functions
createTest('addNumbers(5, 13) = 18', () =>
Expand Down Expand Up @@ -946,6 +960,11 @@ export function getTests(
})
).didThrow()
),
createTest('Getting complex callback from native returns a function', () =>
it(() => testObject.getComplexCallback())
.didNotThrow()
.didReturn('function')
),

// Objects
createTest('getCar()', () =>
Expand Down
11 changes: 9 additions & 2 deletions packages/nitrogen/src/syntax/kotlin/FbjniHybridObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ function getFbjniMethodForwardImplementation(
const name = getHybridObjectName(spec.name)

const returnJNI = new KotlinCxxBridgedType(method.returnType)
const requiresBridge =
returnJNI.needsSpecialHandling ||
method.parameters.some((p) => {
const bridged = new KotlinCxxBridgedType(p.type)
return bridged.needsSpecialHandling
})
const methodName = requiresBridge ? `${method.name}_cxx` : method.name

const returnType = returnJNI.asJniReferenceType('local')
const paramsTypes = method.parameters
Expand All @@ -237,14 +244,14 @@ function getFbjniMethodForwardImplementation(
if (returnJNI.hasType) {
// return something - we need to parse it
body = `
static const auto method = _javaPart->getClass()->getMethod<${cxxSignature}>("${method.name}");
static const auto method = _javaPart->getClass()->getMethod<${cxxSignature}>("${methodName}");
auto __result = method(${paramsForward.join(', ')});
return ${returnJNI.parse('__result', 'kotlin', 'c++', 'c++')};
`
} else {
// void method. no return
body = `
static const auto method = _javaPart->getClass()->getMethod<${cxxSignature}>("${method.name}");
static const auto method = _javaPart->getClass()->getMethod<${cxxSignature}>("${methodName}");
method(${paramsForward.join(', ')});
`
}
Expand Down
93 changes: 78 additions & 15 deletions packages/nitrogen/src/syntax/kotlin/KotlinCxxBridgedType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,24 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {

get needsSpecialHandling(): boolean {
switch (this.type.kind) {
case 'function':
// Function needs to be converted from JFunc_... to Lambda
return true
default:
return false
break
}
// check if any types this type references (e.g. underlying optional, array element, ...)
// needs special handling. if yes, we need it as well
const referencedTypes = getReferencedTypes(this.type)
.filter((t) => t !== this.type)
.map((t) => new KotlinCxxBridgedType(t))
for (const type of referencedTypes) {
if (type.needsSpecialHandling) {
return true
}
}
// no special handling needed
return false
}

getRequiredImports(): SourceImport[] {
Expand Down Expand Up @@ -204,9 +219,10 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
return this.type.getCode(language)
}
case 'array':
const array = getTypeAs(this.type, ArrayType)
const bridgedItem = new KotlinCxxBridgedType(array.itemType)
switch (language) {
case 'c++':
const array = getTypeAs(this.type, ArrayType)
switch (array.itemType.kind) {
case 'number':
return 'jni::JArrayDouble'
Expand All @@ -215,9 +231,10 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
case 'bigint':
return 'jni::JArrayLong'
default:
const bridged = new KotlinCxxBridgedType(array.itemType)
return `jni::JArrayClass<${bridged.getTypeCode(language)}>`
return `jni::JArrayClass<${bridgedItem.getTypeCode(language)}>`
}
case 'kotlin':
return `Array<${bridgedItem.getTypeCode(language)}>`
default:
return this.type.getCode(language)
}
Expand Down Expand Up @@ -313,9 +330,12 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
return this.type.getCode(language)
}
case 'optional': {
const optional = getTypeAs(this.type, OptionalType)
const bridgedWrappingType = new KotlinCxxBridgedType(
optional.wrappingType
)
switch (language) {
case 'c++':
const optional = getTypeAs(this.type, OptionalType)
switch (optional.wrappingType.kind) {
// primitives need to be boxed to make them nullable
case 'number':
Expand All @@ -325,9 +345,10 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
return boxed
default:
// all other types can be nullable as they are objects.
const bridge = new KotlinCxxBridgedType(optional.wrappingType)
return bridge.getTypeCode('c++')
return bridgedWrappingType.getTypeCode('c++')
}
case 'kotlin':
return `${bridgedWrappingType.getTypeCode(language)}?`
default:
return this.type.getCode(language)
}
Expand Down Expand Up @@ -419,9 +440,9 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
switch (language) {
case 'c++':
const func = getTypeAs(this.type, FunctionType)
return `J${func.specializationName}::fromCpp(${parameterName})`
return `J${func.specializationName}_cxx::fromCpp(${parameterName})`
case 'kotlin':
return `${parameterName}.toLambda()`
return parameterName
default:
return parameterName
}
Expand All @@ -442,6 +463,12 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
switch (language) {
case 'c++':
return `${parameterName}.has_value() ? ${bridge.parseFromCppToKotlin(`${parameterName}.value()`, 'c++', true)} : nullptr`
case 'kotlin':
if (bridge.needsSpecialHandling) {
return `${parameterName}?.let { ${bridge.parseFromCppToKotlin('it', language, isBoxed)} }`
} else {
return parameterName
}
default:
return parameterName
}
Expand Down Expand Up @@ -488,11 +515,11 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
}
}
case 'array': {
const array = getTypeAs(this.type, ArrayType)
const arrayType = this.getTypeCode('c++')
const bridge = new KotlinCxxBridgedType(array.itemType)
switch (language) {
case 'c++': {
const array = getTypeAs(this.type, ArrayType)
const arrayType = this.getTypeCode('c++')
const bridge = new KotlinCxxBridgedType(array.itemType)
switch (array.itemType.kind) {
case 'number':
case 'boolean':
Expand All @@ -516,14 +543,20 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
jni::local_ref<${arrayType}> __array = ${arrayType}::newArray(__size);
for (size_t __i = 0; __i < __size; __i++) {
const auto& __element = ${parameterName}[__i];
__array->setElement(__i, *${bridge.parseFromCppToKotlin('__element', 'c++')});
__array->setElement(__i, *${indent(bridge.parseFromCppToKotlin('__element', 'c++'), ' ')});
}
return __array;
}()
`.trim()
}
}
}
case 'kotlin':
if (bridge.needsSpecialHandling) {
return `${parameterName}.map { ${bridge.parseFromCppToKotlin('it', language, isBoxed)} }`
} else {
return parameterName
}
default:
return parameterName
}
Expand Down Expand Up @@ -657,14 +690,44 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
true
)
return `${parameterName} != nullptr ? std::make_optional(${parsed}) : std::nullopt`
case 'kotlin':
if (bridge.needsSpecialHandling) {
return `${parameterName}?.let { ${bridge.parseFromKotlinToCpp('it', language, isBoxed)} }`
} else {
return parameterName
}
default:
return parameterName
}
}
case 'function': {
const functionType = getTypeAs(this.type, FunctionType)
switch (language) {
case 'c++':
return `${parameterName}->cthis()->getFunction()`
case 'c++': {
const returnType = functionType.returnType.getCode('c++')
const params = functionType.parameters.map(
(p) => `${p.getCode('c++')} ${p.escapedName}`
)
const paramsForward = functionType.parameters.map(
(p) => p.escapedName
)
const jniType = `J${functionType.specializationName}_cxx`
return `
[&]() -> ${functionType.getCode('c++')} {
if (${parameterName}->isInstanceOf(${jniType}::javaClassStatic())) [[likely]] {
auto downcast = jni::static_ref_cast<${jniType}::javaobject>(${parameterName});
return downcast->cthis()->getFunction();
} else {
return [${parameterName}](${params.join(', ')}) -> ${returnType} {
return ${parameterName}->invoke(${paramsForward});
};
}
}()
`.trim()
}
case 'kotlin': {
return `${functionType.specializationName}_java(${parameterName})`
}
default:
return parameterName
}
Expand Down
1 change: 1 addition & 0 deletions packages/nitrogen/src/syntax/kotlin/KotlinEnum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ namespace ${cxxNamespace} {
* Convert this Java/Kotlin-based enum to the C++ enum ${enumType.enumName}.
*/
[[maybe_unused]]
[[nodiscard]]
${enumType.enumName} toCpp() const {
static const auto clazz = javaClassStatic();
static const auto fieldOrdinal = clazz->getField<int>("_ordinal");
Expand Down
Loading

0 comments on commit 07d96a0

Please sign in to comment.