UNPKG

nitro-codegen

Version:

The code-generator for react-native-nitro-modules.

856 lines (833 loc) 29 kB
import { NitroConfig } from '../../config/NitroConfig.js' import { indent } from '../../utils.js' import type { BridgedType } from '../BridgedType.js' import { getForwardDeclaration } from '../c++/getForwardDeclaration.js' import { getHybridObjectName, type HybridObjectName, } from '../getHybridObjectName.js' import type { SourceFile, SourceImport } from '../SourceFile.js' import { ArrayType } from '../types/ArrayType.js' import { EnumType } from '../types/EnumType.js' import { FunctionType } from '../types/FunctionType.js' import { getTypeAs } from '../types/getTypeAs.js' import { HybridObjectType } from '../types/HybridObjectType.js' import { OptionalType } from '../types/OptionalType.js' import { PromiseType } from '../types/PromiseType.js' import { RecordType } from '../types/RecordType.js' 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 { getReferencedTypes } from '../getReferencedTypes.js' import { createSwiftCxxHelpers, type SwiftCxxHelper, } from './SwiftCxxTypeHelper.js' import { createSwiftEnumBridge } from './SwiftEnum.js' import { createSwiftStructBridge } from './SwiftStruct.js' import { createSwiftVariant } from './SwiftVariant.js' import { VoidType } from '../types/VoidType.js' import { NamedWrappingType } from '../types/NamedWrappingType.js' import { ErrorType } from '../types/ErrorType.js' import { createSwiftFunctionBridge } from './SwiftFunction.js' // TODO: Remove enum bridge once Swift fixes bidirectional enums crashing the `-Swift.h` header. export class SwiftCxxBridgedType implements BridgedType<'swift', 'c++'> { readonly type: Type private readonly isBridgingToDirectCppTarget: boolean constructor(type: Type, isBridgingToDirectCppTarget: boolean = false) { this.type = type this.isBridgingToDirectCppTarget = isBridgingToDirectCppTarget } get hasType(): boolean { return this.type.kind !== 'void' && this.type.kind !== 'null' } get canBePassedByReference(): boolean { return this.type.canBePassedByReference } get needsSpecialHandling(): boolean { switch (this.type.kind) { case 'enum': // Enums cannot be referenced from C++ <-> Swift bi-directionally, // so we just pass the underlying raw value (int32), and cast from Int <-> Enum. if (this.isBridgingToDirectCppTarget) { // ...unless we bridge directly to a C++ target. Then we don't need special conversion. return false } return true case 'hybrid-object': // Swift HybridObjects need to be wrapped in our own *Cxx Swift classes. // We wrap/unwrap them if needed. return true case 'optional': // swift::Optional<T> <> std::optional<T> return true case 'string': // swift::String <> std::string return true case 'array': // swift::Array<T> <> std::vector<T> return true case 'record': // Dictionary<K, V> <> std::unordered_map<K, V> return true case 'variant': // Variant_A_B_C <> std::variant<A, B, C> return true case 'tuple': // (A, B) <> std::tuple<A, B> return true case 'struct': // SomeStruct (Swift extension) <> SomeStruct (C++) return true case 'function': // (@ecaping () -> Void) <> std::function<...> return true case 'array-buffer': // ArrayBufferHolder <> std::shared_ptr<ArrayBuffer> if (this.isBridgingToDirectCppTarget) { return false } return true case 'date': // Date <> double return true case 'promise': // Promise<T> <> std::shared_ptr<Promise<T>> return true case 'error': // Error <> std.exception_ptr return true case 'map': // AnyMapHolder <> AnyMap return true case 'result-wrapper': // Result<T> <> T return true default: return false } } getRequiredBridge(): SwiftCxxHelper | undefined { // Since Swift doesn't support C++ templates, we need to create helper // functions that create those types (specialized) for us. return createSwiftCxxHelpers(this.type) } private getBridgeOrThrow(): SwiftCxxHelper { const bridge = this.getRequiredBridge() if (bridge == null) throw new Error( `Type ${this.type.kind} requires a bridged specialization!` ) return bridge } getRequiredImports(): SourceImport[] { const imports = this.type.getRequiredImports() if (this.type.kind === 'array-buffer') { imports.push({ name: 'NitroModules/ArrayBufferHolder.hpp', forwardDeclaration: getForwardDeclaration( 'class', 'ArrayBufferHolder', 'NitroModules' ), language: 'c++', space: 'system', }) } // Recursively look into referenced types (e.g. the `T` of a `optional<T>`, or `T` of a `T[]`) const referencedTypes = getReferencedTypes(this.type) referencedTypes.forEach((t) => { if (t === this.type) { // break a recursion - we already know this type return } const bridged = new SwiftCxxBridgedType(t) imports.push(...bridged.getRequiredImports()) }) return imports } getExtraFiles(): SourceFile[] { const files: SourceFile[] = [] switch (this.type.kind) { case 'struct': { const struct = getTypeAs(this.type, StructType) const extensionFile = createSwiftStructBridge(struct) files.push(extensionFile) extensionFile.referencedTypes.forEach((t) => { const bridge = new SwiftCxxBridgedType(t) files.push(...bridge.getExtraFiles()) }) break } case 'enum': { const enumType = getTypeAs(this.type, EnumType) const extensionFile = createSwiftEnumBridge(enumType) files.push(extensionFile) break } case 'function': { const functionType = getTypeAs(this.type, FunctionType) const extensionFile = createSwiftFunctionBridge(functionType) files.push(extensionFile) break } case 'promise': { // Promise needs resolver and rejecter funcs in Swift const promiseType = getTypeAs(this.type, PromiseType) files.push(createSwiftFunctionBridge(promiseType.resolverFunction)) files.push(createSwiftFunctionBridge(promiseType.rejecterFunction)) break } case 'variant': { const variant = getTypeAs(this.type, VariantType) const file = createSwiftVariant(variant) files.push(file) break } } // Recursively look into referenced types (e.g. the `T` of a `optional<T>`, or `T` of a `T[]`) const referencedTypes = getReferencedTypes(this.type) referencedTypes.forEach((t) => { if (t === this.type) { // break a recursion - we already know this type return } const bridged = new SwiftCxxBridgedType(t) files.push(...bridged.getExtraFiles()) }) return files } getTypeCode(language: 'swift' | 'c++'): string { switch (this.type.kind) { case 'enum': if (this.isBridgingToDirectCppTarget) { return this.type.getCode('swift') } switch (language) { case 'c++': return 'int' case 'swift': return 'Int32' default: throw new Error(`Invalid language! ${language}`) } case 'map': { switch (language) { case 'swift': return 'margelo.nitro.TSharedMap' default: return this.type.getCode(language) } } case 'hybrid-object': case 'optional': case 'array': case 'function': case 'variant': case 'tuple': case 'record': case 'result-wrapper': case 'promise': { const bridge = this.getBridgeOrThrow() switch (language) { case 'swift': return `bridge.${bridge.specializationName}` case 'c++': return bridge.cxxType default: return this.type.getCode(language) } } case 'array-buffer': if (this.isBridgingToDirectCppTarget) { return this.type.getCode(language) } else { return `ArrayBufferHolder` } case 'string': { switch (language) { case 'c++': return `std::string` case 'swift': return 'std.string' default: throw new Error(`Invalid language! ${language}`) } } case 'date': switch (language) { case 'swift': return `margelo.nitro.chrono_time` default: return this.type.getCode(language) } case 'error': switch (language) { case 'c++': return 'std::exception_ptr' case 'swift': return 'std.exception_ptr' default: throw new Error(`Invalid language! ${language}`) } default: // No workaround - just return normal type return this.type.getCode(language) } } parse( parameterName: string, from: 'c++' | 'swift', to: 'swift' | 'c++', inLanguage: 'swift' | 'c++' ): string { if (from === 'c++') { return this.parseFromCppToSwift(parameterName, inLanguage) } else if (from === 'swift') { return this.parseFromSwiftToCpp(parameterName, inLanguage) } else { throw new Error(`Cannot parse from ${from} to ${to}!`) } } parseFromCppToSwift( cppParameterName: string, language: 'swift' | 'c++' ): string { switch (this.type.kind) { case 'enum': { if (this.isBridgingToDirectCppTarget) { return cppParameterName } const enumType = getTypeAs(this.type, EnumType) switch (language) { case 'c++': return `static_cast<int>(${cppParameterName})` case 'swift': const fullName = NitroConfig.getCxxNamespace( 'swift', enumType.enumName ) return `${fullName}(rawValue: ${cppParameterName})!` default: throw new Error(`Invalid language! ${language}`) } } case 'hybrid-object': { const bridge = this.getBridgeOrThrow() const getFunc = `bridge.get_${bridge.specializationName}` const name = getTypeHybridObjectName(this.type) switch (language) { case 'swift': return ` { () -> ${name.HybridTSpec} in let __unsafePointer = ${getFunc}(${cppParameterName}) let __instance = ${name.HybridTSpecCxx}.fromUnsafe(__unsafePointer) return __instance.get${name.HybridTSpec}() }()`.trim() default: return cppParameterName } } case 'array-buffer': { switch (language) { case 'swift': if (this.isBridgingToDirectCppTarget) { return `ArrayBufferHolder(${cppParameterName})` } else { return cppParameterName } case 'c++': if (this.isBridgingToDirectCppTarget) { return cppParameterName } else { return `ArrayBufferHolder(${cppParameterName})` } default: return cppParameterName } } case 'promise': { const promise = getTypeAs(this.type, PromiseType) switch (language) { case 'swift': { const bridge = this.getBridgeOrThrow() if (promise.resultingType.kind === 'void') { // It's void - resolve() const resolverFunc = new FunctionType(new VoidType(), []) const rejecterFunc = new FunctionType(new VoidType(), [ new NamedWrappingType('error', new ErrorType()), ]) const resolverFuncBridge = new SwiftCxxBridgedType(resolverFunc) const rejecterFuncBridge = new SwiftCxxBridgedType(rejecterFunc) return ` { () -> ${promise.getCode('swift')} in let __promise = ${promise.getCode('swift')}() let __resolver = { __promise.resolve(withResult: ()) } let __rejecter = { (__error: Error) in __promise.reject(withError: __error) } let __resolverCpp = ${indent(resolverFuncBridge.parseFromSwiftToCpp('__resolver', 'swift'), ' ')} let __rejecterCpp = ${indent(rejecterFuncBridge.parseFromSwiftToCpp('__rejecter', 'swift'), ' ')} let __promiseHolder = bridge.wrap_${bridge.specializationName}(${cppParameterName}) __promiseHolder.addOnResolvedListener(__resolverCpp) __promiseHolder.addOnRejectedListener(__rejecterCpp) return __promise }()`.trim() } else { // It's resolving to a type - resolve(T) const resolverFunc = new FunctionType(new VoidType(), [ new NamedWrappingType('result', promise.resultingType), ]) const rejecterFunc = new FunctionType(new VoidType(), [ new NamedWrappingType('error', new ErrorType()), ]) const resolverFuncBridge = new SwiftCxxBridgedType(resolverFunc) const rejecterFuncBridge = new SwiftCxxBridgedType(rejecterFunc) const resolverFuncName = promise.resultingType .canBePassedByReference ? 'addOnResolvedListener' : 'addOnResolvedListenerCopy' return ` { () -> ${promise.getCode('swift')} in let __promise = ${promise.getCode('swift')}() let __resolver = { (__result: ${promise.resultingType.getCode('swift')}) in __promise.resolve(withResult: __result) } let __rejecter = { (__error: Error) in __promise.reject(withError: __error) } let __resolverCpp = ${indent(resolverFuncBridge.parseFromSwiftToCpp('__resolver', 'swift'), ' ')} let __rejecterCpp = ${indent(rejecterFuncBridge.parseFromSwiftToCpp('__rejecter', 'swift'), ' ')} let __promiseHolder = bridge.wrap_${bridge.specializationName}(${cppParameterName}) __promiseHolder.${resolverFuncName}(__resolverCpp) __promiseHolder.addOnRejectedListener(__rejecterCpp) return __promise }()`.trim() } } default: return cppParameterName } } case 'optional': { const optional = getTypeAs(this.type, OptionalType) const wrapping = new SwiftCxxBridgedType(optional.wrappingType, true) switch (language) { case 'swift': if (wrapping.type.kind === 'enum') { const enumType = getTypeAs(wrapping.type, EnumType) if (enumType.jsType === 'enum') { // TODO: Remove this hack once Swift fixes this shit. // A JS enum is implemented as a number/int based enum. // For some reason, those break in Swift. I have no idea why. return `${cppParameterName}.has_value() ? ${cppParameterName}.pointee : nil` } } if (!wrapping.needsSpecialHandling) { return `${cppParameterName}.value` } return ` { () -> ${optional.getCode('swift')} in if let __unwrapped = ${cppParameterName}.value { return ${indent(wrapping.parseFromCppToSwift('__unwrapped', language), ' ')} } else { return nil } }() `.trim() default: return cppParameterName } } case 'string': { switch (language) { case 'swift': return `String(${cppParameterName})` default: return cppParameterName } } case 'date': { switch (language) { case 'swift': return `Date(fromChrono: ${cppParameterName})`.trim() default: return cppParameterName } } case 'array': { const array = getTypeAs(this.type, ArrayType) const wrapping = new SwiftCxxBridgedType(array.itemType, true) switch (language) { case 'swift': return `${cppParameterName}.map({ __item in ${wrapping.parseFromCppToSwift('__item', 'swift')} })`.trim() default: return cppParameterName } } case 'map': { switch (language) { case 'swift': return `AnyMapHolder(withCppPart: ${cppParameterName})` default: return cppParameterName } } case 'record': { const bridge = this.getBridgeOrThrow() const getKeysFunc = `bridge.get_${bridge.specializationName}_keys` const record = getTypeAs(this.type, RecordType) const wrappingKey = new SwiftCxxBridgedType(record.keyType, true) const wrappingValue = new SwiftCxxBridgedType(record.valueType, true) switch (language) { case 'swift': return ` { () -> ${record.getCode('swift')} in var __dictionary = ${record.getCode('swift')}(minimumCapacity: ${cppParameterName}.size()) let __keys = ${getKeysFunc}(${cppParameterName}) for __key in __keys { let __value = ${cppParameterName}[__key]! __dictionary[${indent(wrappingKey.parseFromCppToSwift('__key', 'swift'), ' ')}] = ${indent(wrappingValue.parseFromCppToSwift('__value', 'swift'), ' ')} } return __dictionary }()`.trim() default: return cppParameterName } } case 'tuple': { switch (language) { case 'swift': return `${cppParameterName}.arg0` default: return cppParameterName } } case 'variant': { const bridge = this.getBridgeOrThrow() const variant = getTypeAs(this.type, VariantType) const valueInitialization = this.isBridgingToDirectCppTarget ? `bridge.${bridge.specializationName}(${cppParameterName})` : cppParameterName const cases = variant.cases .map(([label, v], i) => { const wrapping = new SwiftCxxBridgedType(v, true) const parse = wrapping.parseFromCppToSwift('__actual', 'swift') return ` case ${i}: let __actual = __variant.get_${i}() return .${label}(${indent(parse, ' ')})`.trim() }) .join('\n') switch (language) { case 'swift': return ` { () -> ${variant.getCode('swift')} in let __variant = ${valueInitialization} switch __variant.index() { ${indent(cases, ' ')} default: fatalError("Variant can never have index \\(__variant.index())!") } }()`.trim() default: return cppParameterName } } case 'function': { const funcType = getTypeAs(this.type, FunctionType) switch (language) { case 'swift': const swiftClosureType = funcType.getCode('swift', false) const bridge = this.getBridgeOrThrow() const paramsSignature = funcType.parameters.map( (p) => `__${p.escapedName}: ${p.getCode('swift')}` ) const returnType = funcType.returnType.getCode('swift') const signature = `(${paramsSignature.join(', ')}) -> ${returnType}` const paramsForward = funcType.parameters.map((p) => { const bridged = new SwiftCxxBridgedType(p) return bridged.parseFromSwiftToCpp(`__${p.escapedName}`, 'swift') }) if (funcType.returnType.kind === 'void') { return ` { () -> ${swiftClosureType} in let __wrappedFunction = bridge.wrap_${bridge.specializationName}(${cppParameterName}) return { ${signature} in __wrappedFunction.call(${indent(paramsForward.join(', '), ' ')}) } }()`.trim() } else { const resultBridged = new SwiftCxxBridgedType( funcType.returnType, true ) return ` { () -> ${swiftClosureType} in let __wrappedFunction = bridge.wrap_${bridge.specializationName}(${cppParameterName}) return { ${signature} in let __result = __wrappedFunction.call(${indent(paramsForward.join(', '), ' ')}) return ${indent(resultBridged.parseFromCppToSwift('__result', 'swift'), ' ')} } }()`.trim() } default: return cppParameterName } } case 'error': switch (language) { case 'swift': return `RuntimeError.from(cppError: ${cppParameterName})` default: return cppParameterName } case 'void': // When type is void, don't return anything return '' default: // No workaround - we can just use the value we get from C++ return cppParameterName } } parseFromSwiftToCpp( swiftParameterName: string, language: 'swift' | 'c++' ): string { switch (this.type.kind) { case 'enum': if (this.isBridgingToDirectCppTarget) { return swiftParameterName } switch (language) { case 'c++': return `static_cast<${this.type.getCode('c++')}>(${swiftParameterName})` case 'swift': return `${swiftParameterName}.rawValue` default: throw new Error(`Invalid language! ${language}`) } case 'hybrid-object': { const bridge = this.getBridgeOrThrow() switch (language) { case 'swift': return ` { () -> bridge.${bridge.specializationName} in let __cxxWrapped = ${swiftParameterName}.getCxxWrapper() return __cxxWrapped.getCxxPart() }()`.trim() default: return swiftParameterName } } case 'optional': { const optional = getTypeAs(this.type, OptionalType) const wrapping = new SwiftCxxBridgedType(optional.wrappingType, true) const bridge = this.getBridgeOrThrow() const makeFunc = `bridge.${bridge.funcName}` switch (language) { case 'swift': return ` { () -> bridge.${bridge.specializationName} in if let __unwrappedValue = ${swiftParameterName} { return ${makeFunc}(${indent(wrapping.parseFromSwiftToCpp('__unwrappedValue', language), ' ')}) } else { return .init() } }() `.trim() default: return swiftParameterName } } case 'string': { switch (language) { case 'swift': return `std.string(${swiftParameterName})` default: return swiftParameterName } } case 'date': { switch (language) { case 'swift': return `${swiftParameterName}.toCpp()` default: return swiftParameterName } } case 'array-buffer': { switch (language) { case 'swift': if (this.isBridgingToDirectCppTarget) { return `${swiftParameterName}.getArrayBuffer()` } else { return swiftParameterName } case 'c++': if (this.isBridgingToDirectCppTarget) { return swiftParameterName } else { return `${swiftParameterName}.getArrayBuffer()` } default: return swiftParameterName } } case 'map': { switch (language) { case 'swift': return `${swiftParameterName}.cppPart` default: return swiftParameterName } } case 'promise': { const bridge = this.getBridgeOrThrow() const makePromise = `bridge.${bridge.funcName}` const promise = getTypeAs(this.type, PromiseType) const resolvingType = new SwiftCxxBridgedType( promise.resultingType, true ) switch (language) { case 'swift': const arg = promise.resultingType.kind === 'void' ? '' : resolvingType.parseFromSwiftToCpp('__result', 'swift') return ` { () -> bridge.${bridge.specializationName} in let __promise = ${makePromise}() let __promiseHolder = bridge.wrap_${bridge.specializationName}(__promise) ${swiftParameterName} .then({ __result in __promiseHolder.resolve(${indent(arg, ' ')}) }) .catch({ __error in __promiseHolder.reject(__error.toCpp()) }) return __promise }()`.trim() default: return swiftParameterName } } case 'array': { const bridge = this.getBridgeOrThrow() const makeFunc = `bridge.${bridge.funcName}` const array = getTypeAs(this.type, ArrayType) const wrapping = new SwiftCxxBridgedType(array.itemType, true) switch (language) { case 'swift': return ` { () -> bridge.${bridge.specializationName} in var __vector = ${makeFunc}(${swiftParameterName}.count) for __item in ${swiftParameterName} { __vector.push_back(${indent(wrapping.parseFromSwiftToCpp('__item', language), ' ')}) } return __vector }()`.trim() default: return swiftParameterName } } case 'tuple': { const tuple = getTypeAs(this.type, TupleType) const bridge = this.getBridgeOrThrow() const makeFunc = NitroConfig.getCxxNamespace(language, bridge.funcName) switch (language) { case 'swift': const typesForward = tuple.itemTypes .map((t, i) => { const bridged = new SwiftCxxBridgedType(t) return `${bridged.parseFromSwiftToCpp(`${swiftParameterName}.${i}`, language)}` }) .join(', ') return `${makeFunc}(${typesForward})` default: return swiftParameterName } } case 'variant': { const bridge = this.getBridgeOrThrow() const variant = getTypeAs(this.type, VariantType) switch (language) { case 'swift': const cases = variant.cases .map(([label, v]) => { const wrapping = new SwiftCxxBridgedType(v, true) const parse = wrapping.parseFromSwiftToCpp('__value', 'swift') return ` case .${label}(let __value): return bridge.${bridge.funcName}(${indent(parse, ' ')})`.trim() }) .join('\n') let code = ` { () -> bridge.${bridge.specializationName} in switch ${swiftParameterName} { ${indent(cases, ' ')} } }()`.trim() if (this.isBridgingToDirectCppTarget) { // If we bridge directly to a C++ variant, we need to return the .variant of our wrapper type. code += `.variant` } return code default: return swiftParameterName } } case 'record': { const bridge = this.getBridgeOrThrow() const createMap = `bridge.${bridge.funcName}` const record = getTypeAs(this.type, RecordType) const wrappingKey = new SwiftCxxBridgedType(record.keyType, true) const wrappingValue = new SwiftCxxBridgedType(record.valueType, true) switch (language) { case 'swift': return ` { () -> bridge.${bridge.specializationName} in var __map = ${createMap}(${swiftParameterName}.count) for (__k, __v) in ${swiftParameterName} { bridge.emplace_${bridge.specializationName}(&__map, ${indent(wrappingKey.parseFromSwiftToCpp('__k', 'swift'), ' ')}, ${indent(wrappingValue.parseFromSwiftToCpp('__v', 'swift'), ' ')}) } return __map }()`.trim() default: return swiftParameterName } } case 'function': { switch (language) { case 'swift': { const bridge = this.getBridgeOrThrow() const createFunc = `bridge.${bridge.funcName}` return ` { () -> bridge.${bridge.specializationName} in let __closureWrapper = ${bridge.specializationName}(${swiftParameterName}) return ${createFunc}(__closureWrapper.toUnsafe()) }() `.trim() } default: return swiftParameterName } } case 'error': switch (language) { case 'swift': return `${swiftParameterName}.toCpp()` default: return swiftParameterName } case 'void': // When type is void, don't return anything return '' default: // No workaround - we can just use the value we get from C++ return swiftParameterName } } } function getTypeHybridObjectName(type: Type): HybridObjectName { const hybridObject = getTypeAs(type, HybridObjectType) return getHybridObjectName(hybridObject.hybridObjectName) }