From 658bf73856a3a8cd92c726b54c0505a875a4f0fc Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Wed, 21 Oct 2026 02:04:03 +0200 Subject: [PATCH] refactor(android): collect JNI registrations from Kotlin types --- .../android/createHybridObjectInitializer.ts | 13 ++++-- .../autolinking/createAndroidAutolinking.ts | 39 ++++++++++++++++- .../nitrogen/src/createHybridClassRuntime.ts | 5 +-- packages/nitrogen/src/createPlatformSpec.ts | 4 +- packages/nitrogen/src/syntax/SourceFile.ts | 8 ---- .../src/syntax/c++/CppHybridObjectAdapter.ts | 4 +- packages/nitrogen/src/syntax/createType.ts | 10 ++--- .../src/syntax/kotlin/FbjniHybridObject.ts | 10 ----- .../src/syntax/kotlin/KotlinCallback.ts | 23 +++------- .../src/syntax/kotlin/KotlinConstructor.ts | 4 +- .../syntax/kotlin/KotlinNativeConverter.ts | 40 ++++++++--------- .../src/syntax/kotlin/getKotlinNativeType.ts | 39 ++++++++--------- .../syntax/kotlin/types/KotlinCallbackType.ts | 22 ++++++++-- .../syntax/kotlin/types/KotlinCxxEnumType.ts | 43 +++++++++++++++++++ ...ctType.ts => KotlinCxxHybridObjectType.ts} | 28 +++++++----- ...veStructType.ts => KotlinCxxStructType.ts} | 18 ++++---- .../kotlin/types/KotlinHybridObjectType.ts | 17 +++++++- .../kotlin/types/KotlinNativeEnumType.ts | 34 --------------- .../src/syntax/kotlin/types/KotlinType.ts | 25 ++++++++--- .../views/kotlin/KotlinHybridViewManager.ts | 15 ++----- .../src/views/kotlin/KotlinHybridViewType.ts | 31 +++++++++++++ .../android/NitroTestExternalOnLoad.cpp | 4 +- .../generated/shared/javascript/TestView.cjs | 1 - .../generated/android/NitroTestOnLoad.cpp | 4 +- .../shared/javascript/BenchmarkObjectCpp.cjs | 1 - .../javascript/BenchmarkObjectSwiftKotlin.cjs | 1 - .../shared/javascript/RecyclableTestView.cjs | 1 - .../generated/shared/javascript/TestView.cjs | 1 - .../generated/shared/javascript/Base.cjs | 1 - .../shared/javascript/CppValueSource.cjs | 1 - .../shared/javascript/SomeExternalObject.cjs | 1 - .../generated/shared/javascript/Base.cjs | 1 - .../generated/shared/javascript/Child.cjs | 1 - .../generated/shared/javascript/CppBase.cjs | 1 - .../generated/shared/javascript/CppChild.cjs | 1 - .../shared/javascript/CppGrandchild.cjs | 1 - .../shared/javascript/Grandchild.cjs | 1 - .../shared/javascript/PlatformObject.cjs | 1 - .../shared/javascript/TestObjectCpp.cjs | 1 - .../javascript/TestObjectSwiftKotlin.cjs | 1 - 40 files changed, 265 insertions(+), 192 deletions(-) create mode 100644 packages/nitrogen/src/syntax/kotlin/types/KotlinCxxEnumType.ts rename packages/nitrogen/src/syntax/kotlin/types/{KotlinNativeHybridObjectType.ts => KotlinCxxHybridObjectType.ts} (73%) rename packages/nitrogen/src/syntax/kotlin/types/{KotlinNativeStructType.ts => KotlinCxxStructType.ts} (74%) delete mode 100644 packages/nitrogen/src/syntax/kotlin/types/KotlinNativeEnumType.ts create mode 100644 packages/nitrogen/src/views/kotlin/KotlinHybridViewType.ts diff --git a/packages/nitrogen/src/autolinking/android/createHybridObjectInitializer.ts b/packages/nitrogen/src/autolinking/android/createHybridObjectInitializer.ts index 78f45165c5..8b1f84d69a 100644 --- a/packages/nitrogen/src/autolinking/android/createHybridObjectInitializer.ts +++ b/packages/nitrogen/src/autolinking/android/createHybridObjectInitializer.ts @@ -3,21 +3,26 @@ import { getImportCode, getImportsCode } from '../../syntax/getImportCode.js' import { createFileMetadataString, isNotDuplicate } from '../../syntax/helpers.js' import type { HybridObjectSpec } from '../../syntax/HybridObjectSpec.js' import type { SourceFile } from '../../syntax/SourceFile.js' +import type { JNINativeRegistration } from '../../syntax/kotlin/types/KotlinType.js' import { indent } from '../../utils.js' import { createHybridClassRegistration } from '../createHybridClassRegistration.js' -export function createHybridObjectInitializer({ specs, files }: { specs: HybridObjectSpec[]; files: SourceFile[] }): SourceFile[] { +interface HybridObjectInitializerOptions { + specs: readonly HybridObjectSpec[] + jniNativeRegistrations: readonly JNINativeRegistration[] +} + +export function createHybridObjectInitializer({ specs, jniNativeRegistrations }: HybridObjectInitializerOptions): SourceFile[] { const config = NitroConfig.current const namespace = config.getCxxNamespace('c++') const library = config.getAndroidCxxLibName() const name = `${library}OnLoad` const registrations = specs.map(createHybridClassRegistration) - const natives = files.flatMap((file) => file.jniNativeRegistrations ?? []) const includes = getImportsCode( - [...natives.map((native) => native.import), ...registrations.flatMap((registration) => registration.imports)], + [...jniNativeRegistrations.map((native) => native.import), ...registrations.flatMap((registration) => registration.imports)], 'implementation' ) - const nativeRegistrations = natives + const nativeRegistrations = jniNativeRegistrations .map((native) => `${native.namespace.join('::')}::${native.className}::registerNatives();`) .filter(isNotDuplicate) const classRegistrations = registrations.map((registration) => registration.registration) diff --git a/packages/nitrogen/src/autolinking/createAndroidAutolinking.ts b/packages/nitrogen/src/autolinking/createAndroidAutolinking.ts index d676b8c206..6c4c1a4969 100644 --- a/packages/nitrogen/src/autolinking/createAndroidAutolinking.ts +++ b/packages/nitrogen/src/autolinking/createAndroidAutolinking.ts @@ -1,13 +1,50 @@ import type { HybridObjectSpec } from '../syntax/HybridObjectSpec.js' import type { SourceFile } from '../syntax/SourceFile.js' +import { getAllTypes } from '../syntax/getAllTypes.js' +import { getReferencedTypes } from '../syntax/getReferencedTypes.js' +import { getKotlinNativeType } from '../syntax/kotlin/getKotlinNativeType.js' +import { KotlinHybridObjectType } from '../syntax/kotlin/types/KotlinHybridObjectType.js' +import type { KotlinType } from '../syntax/kotlin/types/KotlinType.js' +import { wrapKotlinType } from '../syntax/kotlin/wrapKotlinType.js' +import { HybridObjectType } from '../syntax/types/HybridObjectType.js' +import { KotlinHybridViewType } from '../views/kotlin/KotlinHybridViewType.js' import type { Autolinking } from './Autolinking.js' import { createCMakeExtension } from './android/createCMakeExtension.js' import { createGradleExtension } from './android/createGradleExtension.js' import { createHybridObjectInitializer } from './android/createHybridObjectInitializer.js' +/** JNI bindings are owned by the Kotlin declarations emitted in this module. */ +function getKotlinTypes(specs: readonly HybridObjectSpec[]): KotlinType[] { + return specs.flatMap((spec) => { + if (spec.config.isExternalConfig) return [] + switch (spec.language) { + case 'kotlin': { + const baseTypes = getKotlinTypes(spec.baseTypes) + let hybridObject: KotlinHybridObjectType + if (spec.isHybridView) { + hybridObject = new KotlinHybridViewType(spec) + } else { + hybridObject = new KotlinHybridObjectType(new HybridObjectType(spec)) + } + // Fabric props start as public C++ types. JNI uses the Kotlin projection. + const cppTypes = getAllTypes(spec).map((type) => getKotlinNativeType(type, spec.config)) + const referencedTypes = cppTypes.flatMap(getReferencedTypes) + const kotlinTypes = referencedTypes.map((type) => wrapKotlinType(type)) + return [...baseTypes, hybridObject, ...kotlinTypes] + } + case 'c++': + case 'swift': + return [] + } + }) +} + export function createAndroidAutolinking(specs: HybridObjectSpec[], files: SourceFile[]): Autolinking { + const kotlinTypes = getKotlinTypes(specs) + const jniNativeRegistrations = kotlinTypes.flatMap((type) => type.getJniNativeRegistrations?.() ?? []) + const initializer = createHybridObjectInitializer({ specs: specs, jniNativeRegistrations: jniNativeRegistrations }) return { platform: 'android', - sourceFiles: [createCMakeExtension(files), createGradleExtension(), ...createHybridObjectInitializer({ specs: specs, files: files })], + sourceFiles: [createCMakeExtension(files), createGradleExtension(), ...initializer], } } diff --git a/packages/nitrogen/src/createHybridClassRuntime.ts b/packages/nitrogen/src/createHybridClassRuntime.ts index 9d79dd0f8e..c48457967f 100644 --- a/packages/nitrogen/src/createHybridClassRuntime.ts +++ b/packages/nitrogen/src/createHybridClassRuntime.ts @@ -123,11 +123,10 @@ function createHybridClassRuntime({ classNames, views = [], outputDirectory }: W initializer: `NitroModules.getHybridClass('${registryName}')`, }) const exportsCode = [classExportCode] + // Getters copied by interop helpers share these module-level caches. const cacheDeclarationsCode = ['let hybridClass'] let declarationsCode: string - let cacheComment: string if (view != null) { - cacheComment = "// Copies of these getters, e.g. from Babel's interop helpers, share the caches." const componentInitializerCode = createComponentInitializer(view) const componentExportCode = createLazyExport({ name: `${name}Component`, @@ -142,7 +141,6 @@ function createHybridClassRuntime({ classNames, views = [], outputDirectory }: W runtimeDirectory: runtimeDirectory, }) } else { - cacheComment = "// Copies of this getter, e.g. from Babel's interop helpers, share the cached class." declarationsCode = ` // The spec's \`export declare class ${name}\` types this runtime export. export {} @@ -153,7 +151,6 @@ ${createFileMetadataString(jsFileName)} const { NitroModules } = require('${NitroConfig.current.getRuntimePackageName()}') -${cacheComment} ${cacheDeclarationsCode.join('\n')} module.exports = { diff --git a/packages/nitrogen/src/createPlatformSpec.ts b/packages/nitrogen/src/createPlatformSpec.ts index b90d90fe5f..952e59fa87 100644 --- a/packages/nitrogen/src/createPlatformSpec.ts +++ b/packages/nitrogen/src/createPlatformSpec.ts @@ -14,7 +14,7 @@ import { Property } from './syntax/Property.js' import { StaticProperty } from './syntax/StaticProperty.js' import { InstanceMethod } from './syntax/InstanceMethod.js' import { HybridObjectType } from './syntax/types/HybridObjectType.js' -import { KotlinNativeHybridObjectType } from './syntax/kotlin/types/KotlinNativeHybridObjectType.js' +import { KotlinCxxHybridObjectType } from './syntax/kotlin/types/KotlinCxxHybridObjectType.js' import { getHybridObjectName } from './syntax/getHybridObjectName.js' import { StaticMethod } from './syntax/StaticMethod.js' import { Constructor } from './syntax/Constructor.js' @@ -212,7 +212,7 @@ export function getHybridObjectSpec( bases.map((base) => new HybridObjectType(base)), config ) - if (target.language === 'kotlin') constructedType = new KotlinNativeHybridObjectType(constructedType) + if (target.language === 'kotlin') constructedType = new KotlinCxxHybridObjectType(constructedType) constructor = new Constructor(getHybridObjectName(name).HybridTSpec, constructedType, constructorParameters) } diff --git a/packages/nitrogen/src/syntax/SourceFile.ts b/packages/nitrogen/src/syntax/SourceFile.ts index 19fbaeae3a..ce93592f40 100644 --- a/packages/nitrogen/src/syntax/SourceFile.ts +++ b/packages/nitrogen/src/syntax/SourceFile.ts @@ -3,12 +3,6 @@ import type { Type } from './types/Type.js' export type CxxHeaderVisibility = 'public' | 'private' -export interface JNINativeRegistration { - readonly namespace: readonly string[] - readonly className: string - readonly import: SourceImport<'c++'> -} - /** * Represents a file with source code, in a specific programming language. */ @@ -38,8 +32,6 @@ export interface SourceFile { * platform bridge headers default to private. */ headerVisibility?: CxxHeaderVisibility - /** Native methods defined by this generated file, registered by its module. */ - jniNativeRegistrations?: readonly JNINativeRegistration[] } /** diff --git a/packages/nitrogen/src/syntax/c++/CppHybridObjectAdapter.ts b/packages/nitrogen/src/syntax/c++/CppHybridObjectAdapter.ts index c0b7650876..0cd287f2c4 100644 --- a/packages/nitrogen/src/syntax/c++/CppHybridObjectAdapter.ts +++ b/packages/nitrogen/src/syntax/c++/CppHybridObjectAdapter.ts @@ -178,7 +178,7 @@ export function createCppHybridObjectAdapter(spec: HybridObjectSpec, platform: P return `${converter}::toSwift(${parameter.type.escapedName})` } case 'kotlin': { - const converter = getKotlinNativeConverter({ cppType: parameter.type, context: 'parameter-type', nativeConfig: spec.config }) + const converter = getKotlinNativeConverter({ cppType: parameter.type, context: 'parameter-type', config: spec.config }) return `${converter}::toKotlin(${parameter.type.escapedName})` } case 'c++': @@ -208,7 +208,7 @@ return ${converter}::fromSwift(__result); `.trim() } case 'kotlin': { - const converter = getKotlinNativeConverter({ cppType: method.returnType, nativeConfig: spec.config }) + const converter = getKotlinNativeConverter({ cppType: method.returnType, config: spec.config }) return ` auto __result = ${call}; return ${converter}::fromKotlin(__result); diff --git a/packages/nitrogen/src/syntax/createType.ts b/packages/nitrogen/src/syntax/createType.ts index 4c7c3022d3..4d3295dabf 100644 --- a/packages/nitrogen/src/syntax/createType.ts +++ b/packages/nitrogen/src/syntax/createType.ts @@ -13,8 +13,8 @@ import { ArrayBufferType } from './types/ArrayBufferType.js' import { EnumType } from './types/EnumType.js' import { HybridObjectType } from './types/HybridObjectType.js' import { StructType } from './types/StructType.js' -import { KotlinNativeStructType } from './kotlin/types/KotlinNativeStructType.js' -import { KotlinNativeHybridObjectType } from './kotlin/types/KotlinNativeHybridObjectType.js' +import { KotlinCxxStructType } from './kotlin/types/KotlinCxxStructType.js' +import { KotlinCxxHybridObjectType } from './kotlin/types/KotlinCxxHybridObjectType.js' import { OptionalType } from './types/OptionalType.js' import { NamedWrappingType } from './types/NamedWrappingType.js' import { getInterfaceProperties } from './getInterfaceProperties.js' @@ -364,7 +364,7 @@ export function createType( // It is another HybridObject being referenced! const reference = getHybridObjectReference(type) const { hybridObjectName: typename, platforms, sourceConfig } = reference - if (language === 'kotlin') return new KotlinNativeHybridObjectType(reference) + if (language === 'kotlin') return new KotlinCxxHybridObjectType(reference) if (language !== 'c++') return reference const options = platforms[platform] if (options == null) { @@ -392,7 +392,7 @@ export function createType( const typename = symbol.getName() const properties = getInterfaceProperties(target, type) const sourceConfig = getHybridObjectNitroModuleConfig(type) ?? NitroConfig.current - if (language === 'kotlin') return new KotlinNativeStructType(typename, properties, sourceConfig) + if (language === 'kotlin') return new KotlinCxxStructType(typename, properties, sourceConfig) return new StructType(typename, properties, sourceConfig) } else if (type.isObject()) { // It is an object. If it has a symbol/name, it is a `type T = ...` declaration, so a `struct`. @@ -403,7 +403,7 @@ export function createType( const typename = symbol.getName() const properties = getInterfaceProperties(target, type) const sourceConfig = getHybridObjectNitroModuleConfig(type) ?? NitroConfig.current - if (language === 'kotlin') return new KotlinNativeStructType(typename, properties, sourceConfig) + if (language === 'kotlin') return new KotlinCxxStructType(typename, properties, sourceConfig) return new StructType(typename, properties, sourceConfig) } else { // It's an anonymous object (`{ ... }`) diff --git a/packages/nitrogen/src/syntax/kotlin/FbjniHybridObject.ts b/packages/nitrogen/src/syntax/kotlin/FbjniHybridObject.ts index f59f170396..8f153a93f2 100644 --- a/packages/nitrogen/src/syntax/kotlin/FbjniHybridObject.ts +++ b/packages/nitrogen/src/syntax/kotlin/FbjniHybridObject.ts @@ -262,16 +262,6 @@ namespace ${cxxNamespace} { }) files.push({ content: cppImplCode, - jniNativeRegistrations: [ - { - namespace: cxxNamespace.split('::'), - className: `${name.JHybridTSpec}::CxxPart`, - import: { - language: 'c++', - declaration: spec.config.getCxxHeader(`${name.JHybridTSpec}.hpp`, 'private'), - }, - }, - ], language: 'c++', name: `${name.JHybridTSpec}.cpp`, subdirectory: [], diff --git a/packages/nitrogen/src/syntax/kotlin/KotlinCallback.ts b/packages/nitrogen/src/syntax/kotlin/KotlinCallback.ts index 265c07fc59..fdd2b4d7d1 100644 --- a/packages/nitrogen/src/syntax/kotlin/KotlinCallback.ts +++ b/packages/nitrogen/src/syntax/kotlin/KotlinCallback.ts @@ -1,4 +1,3 @@ -import { NitroConfig } from '../../config/NitroConfig.js' import { indent } from '../../utils.js' import { createFileMetadataString, isNotDuplicate } from '../helpers.js' import type { SourceFile } from '../SourceFile.js' @@ -10,7 +9,7 @@ export function createKotlinCallback(callbackType: KotlinCallbackType): SourceFi const publicType = callbackType.type const name = callbackType.specializationName const jniName = callbackType.jniName - const packageName = NitroConfig.current.getAndroidPackage('java/kotlin') + const packageName = callbackType.config.getAndroidPackage('java/kotlin') const kotlinReturnType = publicType.returnType.getCode('kotlin', { context: 'return', }) @@ -147,10 +146,10 @@ class ${name}_java(private val callback: ${lambdaSignature}): ${name} { } `.trim() - const jniInterfaceDescriptor = NitroConfig.current.getAndroidPackage('c++/jni', name) - const jniClassDescriptor = NitroConfig.current.getAndroidPackage('c++/jni', `${name}_cxx`) + const jniInterfaceDescriptor = callbackType.config.getAndroidPackage('c++/jni', name) + const jniClassDescriptor = callbackType.config.getAndroidPackage('c++/jni', `${name}_cxx`) const returnType = callbackType.returnType - const cxxNamespace = NitroConfig.current.getCxxNamespace('c++') + const cxxNamespace = callbackType.config.getCxxNamespace('c++') const typename = publicType.getCode('c++', { context: 'value' }) // Inline and owning variants of a signature share this file, so it always // accepts an `InlineCallback` and escapes it into the callback it holds. @@ -241,7 +240,7 @@ return ${cppResult}; [ { language: 'c++', - declaration: NitroConfig.current.getCxxHeader(`${jniName}.hpp`, 'private'), + declaration: callbackType.config.getCxxHeader(`${jniName}.hpp`, 'private'), }, ...imports, ], @@ -350,7 +349,7 @@ namespace ${cxxNamespace} { content: kotlinCode, language: 'kotlin', name: `${name}.kt`, - subdirectory: NitroConfig.current.getAndroidPackageDirectory(), + subdirectory: callbackType.config.getAndroidPackageDirectory(), platform: 'android', }) files.push({ @@ -362,16 +361,6 @@ namespace ${cxxNamespace} { }) files.push({ content: fbjniImplementationCode, - jniNativeRegistrations: [ - { - namespace: cxxNamespace.split('::'), - className: `${jniName}_cxx`, - import: { - language: 'c++', - declaration: NitroConfig.current.getCxxHeader(`${jniName}.hpp`, 'private'), - }, - }, - ], language: 'c++', name: `${jniName}.cpp`, subdirectory: [], diff --git a/packages/nitrogen/src/syntax/kotlin/KotlinConstructor.ts b/packages/nitrogen/src/syntax/kotlin/KotlinConstructor.ts index 2154910999..a5a2a59039 100644 --- a/packages/nitrogen/src/syntax/kotlin/KotlinConstructor.ts +++ b/packages/nitrogen/src/syntax/kotlin/KotlinConstructor.ts @@ -3,7 +3,7 @@ import type { HybridObjectSpec } from '../HybridObjectSpec.js' import { StaticMethod } from '../StaticMethod.js' import { getHybridObjectName } from '../getHybridObjectName.js' import { HybridObjectType } from '../types/HybridObjectType.js' -import { KotlinNativeHybridObjectType } from './types/KotlinNativeHybridObjectType.js' +import { KotlinCxxHybridObjectType } from './types/KotlinCxxHybridObjectType.js' import { createCppJniCallBody, createKotlinBridgeBody, @@ -31,7 +31,7 @@ export class KotlinConstructor extends Constructor { private readonly callable: KotlinMethodInfo constructor({ spec, constructor }: KotlinConstructorOptions) { - const returnType = new KotlinNativeHybridObjectType(new HybridObjectType(spec)) + const returnType = new KotlinCxxHybridObjectType(new HybridObjectType(spec)) const nativeFactory = constructor.toStaticMethod(returnType) const callable = prepareKotlinMethod(nativeFactory) const parameters = callable.parameters.map((parameter) => parameter.kotlin) diff --git a/packages/nitrogen/src/syntax/kotlin/KotlinNativeConverter.ts b/packages/nitrogen/src/syntax/kotlin/KotlinNativeConverter.ts index c80696aaf5..47a1e4d3a4 100644 --- a/packages/nitrogen/src/syntax/kotlin/KotlinNativeConverter.ts +++ b/packages/nitrogen/src/syntax/kotlin/KotlinNativeConverter.ts @@ -10,21 +10,21 @@ import { EnumType } from '../types/EnumType.js' import { StructType } from '../types/StructType.js' import type { Type, TypeContext } from '../types/Type.js' import { getKotlinNativeType } from './getKotlinNativeType.js' -import { KotlinNativeStructType } from './types/KotlinNativeStructType.js' +import { KotlinCxxStructType } from './types/KotlinCxxStructType.js' interface KotlinNativeConverterOptions { cppType: Type context?: TypeContext - nativeConfig?: NitroConfig + config?: NitroConfig } /** Explicit public/private type pair for a native boundary conversion. */ export function getKotlinNativeConverter({ cppType, context = 'value', - nativeConfig = NitroConfig.current, + config = NitroConfig.current, }: KotlinNativeConverterOptions): string { - const nativeType = getKotlinNativeType(cppType, nativeConfig) + const nativeType = getKotlinNativeType(cppType, config) const cppCode = cppType.getCode('c++', { context: context, fullyQualified: true }) const nativeCode = nativeType.getCode('c++', { context: context, fullyQualified: true }) return `JNIConverter<${cppCode}, ${nativeCode}>` @@ -40,41 +40,41 @@ const runtimeImport: SourceImport = { declaration: { header: 'NitroModules/JNIConverter.hpp', space: 'system' }, } -function getConverterFileName(type: ForeignHybridObjectType | StructType | EnumType, nativeConfig: NitroConfig): string { +function getConverterFileName(type: ForeignHybridObjectType | StructType | EnumType, config: NitroConfig): string { if (type instanceof ForeignHybridObjectType) { return `${getCppHybridObjectAdapterNames(type).fileName}+Kotlin` } - if (type instanceof EnumType) return `${type.enumName}+Kotlin+${nativeConfig.getAndroidCxxLibName()}` - return `${type.cppName}+Kotlin+${nativeConfig.getAndroidCxxLibName()}` + if (type instanceof EnumType) return `${type.enumName}+Kotlin+${config.getAndroidCxxLibName()}` + return `${type.cppName}+Kotlin+${config.getAndroidCxxLibName()}` } -function isSpecialized(type: Type, nativeConfig: NitroConfig): type is ForeignHybridObjectType | StructType | EnumType { +function isSpecialized(type: Type, config: NitroConfig): type is ForeignHybridObjectType | StructType | EnumType { return ( type instanceof ForeignHybridObjectType || - (type instanceof StructType && !(type instanceof KotlinNativeStructType)) || - (type instanceof EnumType && nativeConfig.isExternalConfig) + (type instanceof StructType && !(type instanceof KotlinCxxStructType)) || + (type instanceof EnumType && config.isExternalConfig) ) } /** Native-only conversion files. This does not emit JNI bindings or JSI conversions. */ -export function createKotlinNativeConverters(type: Type, nativeConfig: NitroConfig = NitroConfig.current): KotlinNativeConverterFiles { +export function createKotlinNativeConverters(type: Type, config: NitroConfig = NitroConfig.current): KotlinNativeConverterFiles { const referencedTypes = getReferencedTypes(type) - const specializations = referencedTypes.filter((item) => isSpecialized(item, nativeConfig)) - const projectedType = getKotlinNativeType(type, nativeConfig) + const specializations = referencedTypes.filter((item) => isSpecialized(item, config)) + const projectedType = getKotlinNativeType(type, config) const nativeTypeImports = projectedType.getRequiredImports('c++') const imports: SourceImport[] = [runtimeImport, ...nativeTypeImports] const files: SourceFile[] = projectedType.getExtraFiles() const emitted = new Set() for (const cppType of specializations) { - const fileName = getConverterFileName(cppType, nativeConfig) + const fileName = getConverterFileName(cppType, config) if (emitted.has(fileName)) continue emitted.add(fileName) const ownHeader = NitroConfig.current.getCxxHeader(`${fileName}.hpp`, 'private') imports.push({ language: 'c++', declaration: ownHeader }) - const nativeType = getKotlinNativeType(cppType, nativeConfig) + const nativeType = getKotlinNativeType(cppType, config) const cppCode = cppType.getCode('c++', { context: 'value', fullyQualified: true }) const nativeCode = nativeType.getCode('c++', { context: 'value', fullyQualified: true }) - const converterCode = getKotlinNativeConverter({ cppType: cppType, nativeConfig: nativeConfig }) + const converterCode = getKotlinNativeConverter({ cppType: cppType, config: config }) const declarationImports = [runtimeImport, ...cppType.getRequiredImports('c++'), ...nativeType.getRequiredImports('c++')] const implementationImports: SourceImport[] = [ { language: 'c++', declaration: ownHeader }, @@ -106,17 +106,17 @@ return static_cast<${cppCode}>(value); `.trim() } else { const cppToKotlinFields = cppType.properties.map((property) => { - const converter = getKotlinNativeConverter({ cppType: property, nativeConfig: nativeConfig }) + const converter = getKotlinNativeConverter({ cppType: property, config: config }) return `${converter}::toKotlin(value.${property.escapedName})` }) const kotlinToCppFields = cppType.properties.map((property) => { - const converter = getKotlinNativeConverter({ cppType: property, nativeConfig: nativeConfig }) + const converter = getKotlinNativeConverter({ cppType: property, config: config }) return `${converter}::fromKotlin(value.${property.escapedName})` }) const referencedFieldTypes = cppType.properties.flatMap(getReferencedTypes) - const fieldTypes = referencedFieldTypes.filter((item) => isSpecialized(item, nativeConfig)) + const fieldTypes = referencedFieldTypes.filter((item) => isSpecialized(item, config)) for (const fieldType of fieldTypes) { - const fieldFileName = getConverterFileName(fieldType, nativeConfig) + const fieldFileName = getConverterFileName(fieldType, config) implementationImports.push({ language: 'c++', declaration: NitroConfig.current.getCxxHeader(`${fieldFileName}.hpp`, 'private'), diff --git a/packages/nitrogen/src/syntax/kotlin/getKotlinNativeType.ts b/packages/nitrogen/src/syntax/kotlin/getKotlinNativeType.ts index 068aeefeaa..a6d10c08f9 100644 --- a/packages/nitrogen/src/syntax/kotlin/getKotlinNativeType.ts +++ b/packages/nitrogen/src/syntax/kotlin/getKotlinNativeType.ts @@ -1,7 +1,7 @@ import { NitroConfig } from '../../config/NitroConfig.js' import { ArrayType } from '../types/ArrayType.js' import { EnumType } from '../types/EnumType.js' -import { KotlinNativeEnumType } from './types/KotlinNativeEnumType.js' +import { KotlinCxxEnumType } from './types/KotlinCxxEnumType.js' import { CallbackType } from '../types/CallbackType.js' import { HybridObjectType } from '../types/HybridObjectType.js' import { NamedWrappingType } from '../types/NamedWrappingType.js' @@ -12,38 +12,35 @@ import { StructType } from '../types/StructType.js' import { TupleType } from '../types/TupleType.js' import type { Type } from '../types/Type.js' import { VariantType } from '../types/VariantType.js' -import { KotlinNativeHybridObjectType } from './types/KotlinNativeHybridObjectType.js' -import { KotlinNativeStructType } from './types/KotlinNativeStructType.js' +import { KotlinCxxHybridObjectType } from './types/KotlinCxxHybridObjectType.js' +import { KotlinCxxStructType } from './types/KotlinCxxStructType.js' /** The C++ types used inside Kotlin's private JNI bridge, independent of the public C++ API. */ -export function getKotlinNativeType(type: Type, nativeConfig: NitroConfig = NitroConfig.current): Type { +export function getKotlinNativeType(type: Type, config: NitroConfig = NitroConfig.current): Type { if (type instanceof NamedWrappingType) { - return new NamedWrappingType(type.name, getKotlinNativeType(type.type, nativeConfig)) + return new NamedWrappingType(type.name, getKotlinNativeType(type.type, config)) } - if (type instanceof KotlinNativeHybridObjectType || type instanceof KotlinNativeStructType || type instanceof KotlinNativeEnumType) - return type - if (type instanceof EnumType) return new KotlinNativeEnumType(type, nativeConfig) - if (type instanceof HybridObjectType) return new KotlinNativeHybridObjectType(type) + if (type instanceof KotlinCxxHybridObjectType || type instanceof KotlinCxxStructType || type instanceof KotlinCxxEnumType) return type + if (type instanceof EnumType) return new KotlinCxxEnumType(type, config) + if (type instanceof HybridObjectType) return new KotlinCxxHybridObjectType(type) if (type instanceof StructType) { - const properties = type.properties.map((property) => new NamedWrappingType(property.name, getKotlinNativeType(property, nativeConfig))) - return new KotlinNativeStructType(type.structName, properties, type.sourceConfig, nativeConfig) + const properties = type.properties.map((property) => new NamedWrappingType(property.name, getKotlinNativeType(property, config))) + return new KotlinCxxStructType(type.structName, properties, type.sourceConfig, config) } - if (type instanceof ArrayType) return new ArrayType(getKotlinNativeType(type.itemType, nativeConfig)) - if (type instanceof OptionalType) return new OptionalType(getKotlinNativeType(type.wrappingType, nativeConfig)) - if (type instanceof PromiseType) return new PromiseType(getKotlinNativeType(type.resultingType, nativeConfig)) + if (type instanceof ArrayType) return new ArrayType(getKotlinNativeType(type.itemType, config)) + if (type instanceof OptionalType) return new OptionalType(getKotlinNativeType(type.wrappingType, config)) + if (type instanceof PromiseType) return new PromiseType(getKotlinNativeType(type.resultingType, config)) if (type instanceof RecordType) { - return new RecordType(getKotlinNativeType(type.keyType, nativeConfig), getKotlinNativeType(type.valueType, nativeConfig)) + return new RecordType(getKotlinNativeType(type.keyType, config), getKotlinNativeType(type.valueType, config)) } if (type instanceof VariantType) { - const variants = type.variants.map((item) => getKotlinNativeType(item, nativeConfig)) + const variants = type.variants.map((item) => getKotlinNativeType(item, config)) return new VariantType(variants, type.aliasName) } - if (type instanceof TupleType) return new TupleType(type.itemTypes.map((item) => getKotlinNativeType(item, nativeConfig))) + if (type instanceof TupleType) return new TupleType(type.itemTypes.map((item) => getKotlinNativeType(item, config))) if (type instanceof CallbackType) { - const parameters = type.parameters.map( - (parameter) => new NamedWrappingType(parameter.name, getKotlinNativeType(parameter, nativeConfig)) - ) - return new CallbackType(getKotlinNativeType(type.returnType, nativeConfig), parameters, type.isInline) + const parameters = type.parameters.map((parameter) => new NamedWrappingType(parameter.name, getKotlinNativeType(parameter, config))) + return new CallbackType(getKotlinNativeType(type.returnType, config), parameters, type.isInline) } return type } diff --git a/packages/nitrogen/src/syntax/kotlin/types/KotlinCallbackType.ts b/packages/nitrogen/src/syntax/kotlin/types/KotlinCallbackType.ts index e22a25cee3..9c2b98eecc 100644 --- a/packages/nitrogen/src/syntax/kotlin/types/KotlinCallbackType.ts +++ b/packages/nitrogen/src/syntax/kotlin/types/KotlinCallbackType.ts @@ -5,7 +5,7 @@ import { CallbackType, type GetCallbackCodeOptions } from '../../types/CallbackT import { createKotlinCallback } from '../KotlinCallback.js' import { wrapKotlinType } from '../wrapKotlinType.js' import { KotlinNamedType } from './KotlinNamedType.js' -import { getJniReferenceCode, type KotlinConversion, type KotlinType } from './KotlinType.js' +import { getJniReferenceCode, type JNINativeRegistration, type KotlinConversion, type KotlinType } from './KotlinType.js' /** * JNI represents a callback as a Kotlin `fun interface`. @@ -17,7 +17,10 @@ export class KotlinCallbackType extends CallbackType implements KotlinType { readonly jniValueKind = 'pointer' readonly needsKotlinConversion = true - constructor(readonly type: CallbackType) { + constructor( + readonly type: CallbackType, + readonly config: NitroConfig = NitroConfig.current + ) { const returnType = wrapKotlinType(type.returnType) const parameters = type.parameters.map((p) => new KotlinNamedType(p.name, wrapKotlinType(p))) super(returnType, parameters, type.isInline) @@ -83,6 +86,19 @@ export class KotlinCallbackType extends CallbackType implements KotlinType { } } + getJniNativeRegistrations(): readonly JNINativeRegistration[] { + return [ + { + namespace: this.config.getCxxNamespace('c++').split('::'), + className: `${this.jniName}_cxx`, + import: { + language: 'c++', + declaration: this.config.getCxxHeader(`${this.jniName}.hpp`, 'private'), + }, + }, + ] + } + override getExtraFiles(): SourceFile[] { return [...super.getExtraFiles(), ...createKotlinCallback(this)] } @@ -94,7 +110,7 @@ export class KotlinCallbackType extends CallbackType implements KotlinType { imports.push( { language: language, - declaration: NitroConfig.current.getCxxHeader(`${this.jniName}.hpp`, 'private'), + declaration: this.config.getCxxHeader(`${this.jniName}.hpp`, 'private'), }, { language: language, diff --git a/packages/nitrogen/src/syntax/kotlin/types/KotlinCxxEnumType.ts b/packages/nitrogen/src/syntax/kotlin/types/KotlinCxxEnumType.ts new file mode 100644 index 0000000000..16b1237dd1 --- /dev/null +++ b/packages/nitrogen/src/syntax/kotlin/types/KotlinCxxEnumType.ts @@ -0,0 +1,43 @@ +import { NitroConfig } from '../../../config/NitroConfig.js' +import type { Language } from '../../../getPlatformSpecs.js' +import type { SourceFile, SourceImport } from '../../SourceFile.js' +import { EnumType } from '../../types/EnumType.js' +import type { GetCodeOptions } from '../../types/Type.js' + +/** Enum ownership follows the module whose private Kotlin bridge is being called. */ +export class KotlinCxxEnumType extends EnumType { + readonly config: NitroConfig + + constructor(type: EnumType, config: NitroConfig = NitroConfig.current) { + super(type) + this.config = config + } + + override getCode(language: Language, options: GetCodeOptions): string { + switch (language) { + case 'c++': + if (options.fullyQualified || this.config.isExternalConfig) { + return this.config.getCxxNamespace('c++', this.enumName) + } + return super.getCode(language, options) + case 'kotlin': + case 'swift': + return super.getCode(language, options) + } + } + + override getRequiredImports(language: Language): SourceImport[] { + switch (language) { + case 'c++': + return [{ language: 'c++', declaration: this.config.getCxxHeader(this.declarationFile.name) }] + case 'kotlin': + case 'swift': + return super.getRequiredImports(language) + } + } + + override getExtraFiles(): SourceFile[] { + if (this.config.isExternalConfig) return [] + return super.getExtraFiles() + } +} diff --git a/packages/nitrogen/src/syntax/kotlin/types/KotlinNativeHybridObjectType.ts b/packages/nitrogen/src/syntax/kotlin/types/KotlinCxxHybridObjectType.ts similarity index 73% rename from packages/nitrogen/src/syntax/kotlin/types/KotlinNativeHybridObjectType.ts rename to packages/nitrogen/src/syntax/kotlin/types/KotlinCxxHybridObjectType.ts index 5b5de00af4..ac0bebf6c4 100644 --- a/packages/nitrogen/src/syntax/kotlin/types/KotlinNativeHybridObjectType.ts +++ b/packages/nitrogen/src/syntax/kotlin/types/KotlinCxxHybridObjectType.ts @@ -5,7 +5,7 @@ import type { SourceImport } from '../../SourceFile.js' import { HybridObjectType, type GetHybridObjectCodeOptions } from '../../types/HybridObjectType.js' /** Private C++ representation used by Kotlin's JSI/JNI fallback bridge. */ -export class KotlinNativeHybridObjectType extends HybridObjectType { +export class KotlinCxxHybridObjectType extends HybridObjectType { constructor(type: HybridObjectType) { super(type.hybridObjectName, type.platforms, type.baseTypes, type.sourceConfig) } @@ -38,15 +38,21 @@ export class KotlinNativeHybridObjectType extends HybridObjectType { } override getRequiredImports(language: Language): SourceImport[] { - if (language !== 'c++') return super.getRequiredImports(language) - const { JHybridTSpec } = getHybridObjectName(this.hybridObjectName) - return [ - { language: 'c++', declaration: { header: 'memory', space: 'system' } }, - { - language: 'c++', - declaration: { forwardDeclaration: getForwardDeclaration('class', JHybridTSpec, this.sourceConfig.getCxxNamespace('c++')) }, - implementation: this.getCppHeader(), - }, - ] + switch (language) { + case 'c++': { + const { JHybridTSpec } = getHybridObjectName(this.hybridObjectName) + return [ + { language: 'c++', declaration: { header: 'memory', space: 'system' } }, + { + language: 'c++', + declaration: { forwardDeclaration: getForwardDeclaration('class', JHybridTSpec, this.sourceConfig.getCxxNamespace('c++')) }, + implementation: this.getCppHeader(), + }, + ] + } + case 'kotlin': + case 'swift': + return super.getRequiredImports(language) + } } } diff --git a/packages/nitrogen/src/syntax/kotlin/types/KotlinNativeStructType.ts b/packages/nitrogen/src/syntax/kotlin/types/KotlinCxxStructType.ts similarity index 74% rename from packages/nitrogen/src/syntax/kotlin/types/KotlinNativeStructType.ts rename to packages/nitrogen/src/syntax/kotlin/types/KotlinCxxStructType.ts index 0842c42834..b6a9227d70 100644 --- a/packages/nitrogen/src/syntax/kotlin/types/KotlinNativeStructType.ts +++ b/packages/nitrogen/src/syntax/kotlin/types/KotlinCxxStructType.ts @@ -6,25 +6,25 @@ import { StructType } from '../../types/StructType.js' import type { GetCodeOptions, NamedType } from '../../types/Type.js' /** Keeps Kotlin fallback fields independent of the public C++ struct projection. */ -export class KotlinNativeStructType extends StructType { - readonly nativeConfig: NitroConfig +export class KotlinCxxStructType extends StructType { + readonly config: NitroConfig constructor( structName: string, properties: NamedType[], sourceConfig: NitroConfig = NitroConfig.current, - nativeConfig: NitroConfig = NitroConfig.current + config: NitroConfig = NitroConfig.current ) { super(structName, properties, sourceConfig, { cppName: `${structName}Kotlin`, platform: 'android', headerVisibility: 'private' }) - this.nativeConfig = nativeConfig + this.config = config } override getCode(language: Language, options: GetCodeOptions): string { switch (language) { case 'c++': { let name: string - if (options.fullyQualified || this.nativeConfig.isExternalConfig) { - name = this.nativeConfig.getCxxNamespace('c++', this.cppName) + if (options.fullyQualified || this.config.isExternalConfig) { + name = this.config.getCxxNamespace('c++', this.cppName) } else { name = this.cppName } @@ -42,8 +42,8 @@ export class KotlinNativeStructType extends StructType { { language: 'c++', declaration: { - ...this.nativeConfig.getCxxHeader(`${this.cppName}.hpp`, 'private'), - forwardDeclaration: getForwardDeclaration('struct', this.cppName, this.nativeConfig.getCxxNamespace('c++')), + ...this.config.getCxxHeader(`${this.cppName}.hpp`, 'private'), + forwardDeclaration: getForwardDeclaration('struct', this.cppName, this.config.getCxxNamespace('c++')), }, }, ] @@ -53,7 +53,7 @@ export class KotlinNativeStructType extends StructType { } override getExtraFiles(): SourceFile[] { - if (this.nativeConfig.isExternalConfig) return [] + if (this.config.isExternalConfig) return [] return super.getExtraFiles() } } diff --git a/packages/nitrogen/src/syntax/kotlin/types/KotlinHybridObjectType.ts b/packages/nitrogen/src/syntax/kotlin/types/KotlinHybridObjectType.ts index 03b2661592..6940f25a70 100644 --- a/packages/nitrogen/src/syntax/kotlin/types/KotlinHybridObjectType.ts +++ b/packages/nitrogen/src/syntax/kotlin/types/KotlinHybridObjectType.ts @@ -3,7 +3,7 @@ import type { GetCodeOptions } from '../../types/Type.js' import { getHybridObjectName } from '../../getHybridObjectName.js' import type { SourceImport } from '../../SourceFile.js' import { HybridObjectType } from '../../types/HybridObjectType.js' -import { getJniReferenceCode, type KotlinConversion, type KotlinType } from './KotlinType.js' +import { getJniReferenceCode, type JNINativeRegistration, type KotlinConversion, type KotlinType } from './KotlinType.js' export class KotlinHybridObjectType extends HybridObjectType implements KotlinType { readonly jniValueKind = 'wrapper' @@ -71,6 +71,21 @@ export class KotlinHybridObjectType extends HybridObjectType implements KotlinTy } } + getJniNativeRegistrations(): readonly JNINativeRegistration[] { + if (this.sourceConfig.isExternalConfig) return [] + const name = getHybridObjectName(this.hybridObjectName) + return [ + { + namespace: this.sourceConfig.getCxxNamespace('c++').split('::'), + className: `${name.JHybridTSpec}::CxxPart`, + import: { + language: 'c++', + declaration: this.sourceConfig.getCxxHeader(`${name.JHybridTSpec}.hpp`, 'private'), + }, + }, + ] + } + override getRequiredImports(language: Language): SourceImport[] { const imports = this.type.getRequiredImports(language) switch (language) { diff --git a/packages/nitrogen/src/syntax/kotlin/types/KotlinNativeEnumType.ts b/packages/nitrogen/src/syntax/kotlin/types/KotlinNativeEnumType.ts deleted file mode 100644 index 9f1f87fd22..0000000000 --- a/packages/nitrogen/src/syntax/kotlin/types/KotlinNativeEnumType.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { NitroConfig } from '../../../config/NitroConfig.js' -import type { Language } from '../../../getPlatformSpecs.js' -import type { SourceFile, SourceImport } from '../../SourceFile.js' -import { EnumType } from '../../types/EnumType.js' -import type { GetCodeOptions } from '../../types/Type.js' - -/** Enum ownership follows the module whose private Kotlin bridge is being called. */ -export class KotlinNativeEnumType extends EnumType { - readonly nativeConfig: NitroConfig - - constructor(type: EnumType, nativeConfig: NitroConfig = NitroConfig.current) { - super(type) - this.nativeConfig = nativeConfig - } - - override getCode(language: Language, options: GetCodeOptions): string { - if (language === 'c++' && (options.fullyQualified || this.nativeConfig.isExternalConfig)) { - return this.nativeConfig.getCxxNamespace('c++', this.enumName) - } - return super.getCode(language, options) - } - - override getRequiredImports(language: Language): SourceImport[] { - if (language === 'c++') { - return [{ language: 'c++', declaration: this.nativeConfig.getCxxHeader(this.declarationFile.name) }] - } - return super.getRequiredImports(language) - } - - override getExtraFiles(): SourceFile[] { - if (this.nativeConfig.isExternalConfig) return [] - return super.getExtraFiles() - } -} diff --git a/packages/nitrogen/src/syntax/kotlin/types/KotlinType.ts b/packages/nitrogen/src/syntax/kotlin/types/KotlinType.ts index edfacf8ce2..801bd80490 100644 --- a/packages/nitrogen/src/syntax/kotlin/types/KotlinType.ts +++ b/packages/nitrogen/src/syntax/kotlin/types/KotlinType.ts @@ -1,5 +1,13 @@ +import type { SourceImport } from '../../SourceFile.js' import type { Type, TypeContext } from '../../types/Type.js' +/** A generated JNI class whose native methods must be registered by its module. */ +export interface JNINativeRegistration { + readonly namespace: readonly string[] + readonly className: string + readonly import: SourceImport<'c++'> +} + export interface KotlinTypeOptions { /** * Whether JNI holds the value as an object, as in optionals, records, promise @@ -49,6 +57,8 @@ export interface KotlinType extends Type { readonly fromCppReturnType?: string /** Whether the public Kotlin API needs conversion to this representation. */ readonly needsKotlinConversion: boolean + /** Native methods required by this type, owned by the module that declares them. */ + getJniNativeRegistrations?(): readonly JNINativeRegistration[] /** Converts `value` across JNI, see `KotlinConversion`. */ convert(value: string, conversion: KotlinConversion): string } @@ -82,11 +92,16 @@ export function getJniPrimitiveCode(jniType: string, context: TypeContext, { box /** Boxes or unboxes a JNI primitive in C++ where JNI requires an object. */ export function convertJniPrimitive(value: string, { from, in: language }: KotlinConversion, { boxedType, boxed }: JniPrimitive): string { - if (language !== 'c++' || !boxed) return value - switch (from) { - case 'c++': - return `${boxedType}::valueOf(${value})` + switch (language) { case 'kotlin': - return `${value}->value()` + return value + case 'c++': + if (!boxed) return value + switch (from) { + case 'c++': + return `${boxedType}::valueOf(${value})` + case 'kotlin': + return `${value}->value()` + } } } diff --git a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts index 5c67ecc618..026568dbcb 100644 --- a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts +++ b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts @@ -1,3 +1,4 @@ +import { KotlinHybridViewType } from './KotlinHybridViewType.js' import type { SourceFile } from '../../syntax/SourceFile.js' import type { HybridObjectSpec } from '../../syntax/HybridObjectSpec.js' import { @@ -20,7 +21,7 @@ export function createKotlinHybridViewManager(spec: HybridObjectSpec<'kotlin'>): const cxxNamespace = spec.config.getCxxNamespace('c++', 'views') const { JHybridTSpec, HybridTSpec } = getHybridObjectName(spec.name) const { manager, stateClassName, component, propsClassName, descriptorClassName } = getViewComponentNames(spec) - const stateUpdaterName = `${stateClassName}Updater` + const { stateUpdaterName } = new KotlinHybridViewType(spec) const implementation = getKotlinImplementation(spec) const viewImplementation = implementation.className const implementationImport = getImportCode(implementation.kotlinImport) @@ -220,7 +221,7 @@ public: const propsUpdaterCalls = viewProperties.map(({ property, config }) => { const name = escapeCppName(property.name) const setter = property.getSetterName('other') - const converter = getKotlinNativeConverter({ cppType: property.type, nativeConfig: config }) + const converter = getKotlinNativeConverter({ cppType: property.type, config: config }) return ` if (oldProps == nullptr ? newProps->${name}.isProvided() @@ -339,16 +340,6 @@ void J${stateUpdaterName}::updateViewProps(jni::alias_ref /* class name: `J${stateUpdaterName}.cpp`, platform: 'android', subdirectory: ['views'], - jniNativeRegistrations: [ - { - namespace: cxxNamespace.split('::'), - className: `J${stateUpdaterName}`, - import: { - language: 'c++', - declaration: updaterHeader, - }, - }, - ], }, ] } diff --git a/packages/nitrogen/src/views/kotlin/KotlinHybridViewType.ts b/packages/nitrogen/src/views/kotlin/KotlinHybridViewType.ts new file mode 100644 index 0000000000..3cc3f48b8b --- /dev/null +++ b/packages/nitrogen/src/views/kotlin/KotlinHybridViewType.ts @@ -0,0 +1,31 @@ +import type { HybridObjectSpec } from '../../syntax/HybridObjectSpec.js' +import { KotlinHybridObjectType } from '../../syntax/kotlin/types/KotlinHybridObjectType.js' +import type { JNINativeRegistration } from '../../syntax/kotlin/types/KotlinType.js' +import { HybridObjectType } from '../../syntax/types/HybridObjectType.js' +import { getViewComponentNames } from '../CppHybridViewComponent.js' + +/** A Kotlin HybridView also owns the JNI entry point that applies Fabric props. */ +export class KotlinHybridViewType extends KotlinHybridObjectType { + readonly stateUpdaterName: string + + constructor(spec: HybridObjectSpec<'kotlin'>) { + super(new HybridObjectType(spec)) + const { stateClassName } = getViewComponentNames(spec) + this.stateUpdaterName = `${stateClassName}Updater` + } + + override getJniNativeRegistrations(): readonly JNINativeRegistration[] { + if (this.sourceConfig.isExternalConfig) return [] + return [ + ...super.getJniNativeRegistrations(), + { + namespace: this.sourceConfig.getCxxNamespace('c++', 'views').split('::'), + className: `J${this.stateUpdaterName}`, + import: { + language: 'c++', + declaration: this.sourceConfig.getCxxHeader(`views/J${this.stateUpdaterName}.hpp`, 'private'), + }, + }, + ] + } +} diff --git a/tests/react-native/packages/react-native-nitro-test-external/nitrogen/generated/android/NitroTestExternalOnLoad.cpp b/tests/react-native/packages/react-native-nitro-test-external/nitrogen/generated/android/NitroTestExternalOnLoad.cpp index 98f315a449..370f3129ef 100644 --- a/tests/react-native/packages/react-native-nitro-test-external/nitrogen/generated/android/NitroTestExternalOnLoad.cpp +++ b/tests/react-native/packages/react-native-nitro-test-external/nitrogen/generated/android/NitroTestExternalOnLoad.cpp @@ -8,14 +8,14 @@ #include #include #include -#include #include +#include namespace margelo::nitro::test::external { void registerAllNatives() { margelo::nitro::test::external::JHybridTestViewSpec::CxxPart::registerNatives(); - margelo::nitro::test::external::JCallback_void_cxx::registerNatives(); margelo::nitro::test::external::views::JHybridTestViewStateUpdater::registerNatives(); + margelo::nitro::test::external::JCallback_void_cxx::registerNatives(); HybridClassRegistry::registerHybridClass("test.external.TestView", JHybridTestViewSpec::Class::get()); } diff --git a/tests/react-native/packages/react-native-nitro-test-external/nitrogen/generated/shared/javascript/TestView.cjs b/tests/react-native/packages/react-native-nitro-test-external/nitrogen/generated/shared/javascript/TestView.cjs index d16a928393..2ea74ff361 100644 --- a/tests/react-native/packages/react-native-nitro-test-external/nitrogen/generated/shared/javascript/TestView.cjs +++ b/tests/react-native/packages/react-native-nitro-test-external/nitrogen/generated/shared/javascript/TestView.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('react-native-nitro-modules') -// Copies of these getters, e.g. from Babel's interop helpers, share the caches. let hybridClass let hybridComponent diff --git a/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/android/NitroTestOnLoad.cpp b/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/android/NitroTestOnLoad.cpp index bfe6a9e99c..687d461527 100644 --- a/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/android/NitroTestOnLoad.cpp +++ b/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/android/NitroTestOnLoad.cpp @@ -12,8 +12,8 @@ #include #include #include -#include #include +#include #include namespace margelo::nitro::test { @@ -23,8 +23,8 @@ namespace margelo::nitro::test { margelo::nitro::test::JHybridRecyclableTestViewSpec::CxxPart::registerNatives(); margelo::nitro::test::views::JHybridRecyclableTestViewStateUpdater::registerNatives(); margelo::nitro::test::JHybridTestViewSpec::CxxPart::registerNatives(); - margelo::nitro::test::JCallback_void_cxx::registerNatives(); margelo::nitro::test::views::JHybridTestViewStateUpdater::registerNatives(); + margelo::nitro::test::JCallback_void_cxx::registerNatives(); HybridClassRegistry::registerHybridClass("test.BenchmarkObjectCpp", HybridBenchmarkObjectCppSpec::Class::get()); HybridClassRegistry::registerHybridClass("test.BenchmarkObjectSwiftKotlin", JHybridBenchmarkObjectSwiftKotlinSpec::Class::get()); diff --git a/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/BenchmarkObjectCpp.cjs b/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/BenchmarkObjectCpp.cjs index 765fd600bb..a042633b39 100644 --- a/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/BenchmarkObjectCpp.cjs +++ b/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/BenchmarkObjectCpp.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('react-native-nitro-modules') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = { diff --git a/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/BenchmarkObjectSwiftKotlin.cjs b/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/BenchmarkObjectSwiftKotlin.cjs index 988b3a5a5e..27c4d06146 100644 --- a/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/BenchmarkObjectSwiftKotlin.cjs +++ b/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/BenchmarkObjectSwiftKotlin.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('react-native-nitro-modules') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = { diff --git a/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/RecyclableTestView.cjs b/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/RecyclableTestView.cjs index e96af40df5..faa81d228b 100644 --- a/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/RecyclableTestView.cjs +++ b/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/RecyclableTestView.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('react-native-nitro-modules') -// Copies of these getters, e.g. from Babel's interop helpers, share the caches. let hybridClass let hybridComponent diff --git a/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/TestView.cjs b/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/TestView.cjs index dd4c358862..b09a0a0e0d 100644 --- a/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/TestView.cjs +++ b/tests/react-native/packages/react-native-nitro-test/nitrogen/generated/shared/javascript/TestView.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('react-native-nitro-modules') -// Copies of these getters, e.g. from Babel's interop helpers, share the caches. let hybridClass let hybridComponent diff --git a/tests/standalone/nitro-test-external/nitrogen/generated/shared/javascript/Base.cjs b/tests/standalone/nitro-test-external/nitrogen/generated/shared/javascript/Base.cjs index a4bc7b6183..54183d41bb 100644 --- a/tests/standalone/nitro-test-external/nitrogen/generated/shared/javascript/Base.cjs +++ b/tests/standalone/nitro-test-external/nitrogen/generated/shared/javascript/Base.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('nitro-core') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = { diff --git a/tests/standalone/nitro-test-external/nitrogen/generated/shared/javascript/CppValueSource.cjs b/tests/standalone/nitro-test-external/nitrogen/generated/shared/javascript/CppValueSource.cjs index a7ce629c19..7dba69a0b4 100644 --- a/tests/standalone/nitro-test-external/nitrogen/generated/shared/javascript/CppValueSource.cjs +++ b/tests/standalone/nitro-test-external/nitrogen/generated/shared/javascript/CppValueSource.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('nitro-core') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = { diff --git a/tests/standalone/nitro-test-external/nitrogen/generated/shared/javascript/SomeExternalObject.cjs b/tests/standalone/nitro-test-external/nitrogen/generated/shared/javascript/SomeExternalObject.cjs index 61cea70910..1797242b1c 100644 --- a/tests/standalone/nitro-test-external/nitrogen/generated/shared/javascript/SomeExternalObject.cjs +++ b/tests/standalone/nitro-test-external/nitrogen/generated/shared/javascript/SomeExternalObject.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('nitro-core') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = { diff --git a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/Base.cjs b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/Base.cjs index 872c7a1a30..c50b168402 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/Base.cjs +++ b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/Base.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('nitro-core') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = { diff --git a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/Child.cjs b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/Child.cjs index e3309c4159..c98589cdd6 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/Child.cjs +++ b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/Child.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('nitro-core') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = { diff --git a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/CppBase.cjs b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/CppBase.cjs index b179978f4d..aa68f74bb0 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/CppBase.cjs +++ b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/CppBase.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('nitro-core') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = { diff --git a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/CppChild.cjs b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/CppChild.cjs index 66173bdac8..906cf1d9b5 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/CppChild.cjs +++ b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/CppChild.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('nitro-core') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = { diff --git a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/CppGrandchild.cjs b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/CppGrandchild.cjs index 0ce8257bc8..ad441a5304 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/CppGrandchild.cjs +++ b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/CppGrandchild.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('nitro-core') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = { diff --git a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/Grandchild.cjs b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/Grandchild.cjs index a6feccd7d8..0f28e3a5ea 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/Grandchild.cjs +++ b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/Grandchild.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('nitro-core') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = { diff --git a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/PlatformObject.cjs b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/PlatformObject.cjs index 7e09bef607..057396c29e 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/PlatformObject.cjs +++ b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/PlatformObject.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('nitro-core') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = { diff --git a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/TestObjectCpp.cjs b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/TestObjectCpp.cjs index 2558303282..e4c5769e21 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/TestObjectCpp.cjs +++ b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/TestObjectCpp.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('nitro-core') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = { diff --git a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/TestObjectSwiftKotlin.cjs b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/TestObjectSwiftKotlin.cjs index e0c79149cc..d663dbd000 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/TestObjectSwiftKotlin.cjs +++ b/tests/standalone/nitro-test/nitrogen/generated/shared/javascript/TestObjectSwiftKotlin.cjs @@ -7,7 +7,6 @@ const { NitroModules } = require('nitro-core') -// Copies of this getter, e.g. from Babel's interop helpers, share the cached class. let hybridClass module.exports = {