nitro-codegen
Version:
The code-generator for react-native-nitro-modules.
805 lines (804 loc) • 34.2 kB
JavaScript
import { NitroConfig } from '../../config/NitroConfig.js';
import { indent } from '../../utils.js';
import { getForwardDeclaration } from '../c++/getForwardDeclaration.js';
import { getHybridObjectName, } from '../getHybridObjectName.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 { VariantType } from '../types/VariantType.js';
import { getReferencedTypes } from '../getReferencedTypes.js';
import { createSwiftCxxHelpers, } 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 {
type;
isBridgingToDirectCppTarget;
constructor(type, isBridgingToDirectCppTarget = false) {
this.type = type;
this.isBridgingToDirectCppTarget = isBridgingToDirectCppTarget;
}
get hasType() {
return this.type.kind !== 'void' && this.type.kind !== 'null';
}
get canBePassedByReference() {
return this.type.canBePassedByReference;
}
get needsSpecialHandling() {
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() {
// Since Swift doesn't support C++ templates, we need to create helper
// functions that create those types (specialized) for us.
return createSwiftCxxHelpers(this.type);
}
getBridgeOrThrow() {
const bridge = this.getRequiredBridge();
if (bridge == null)
throw new Error(`Type ${this.type.kind} requires a bridged specialization!`);
return bridge;
}
getRequiredImports() {
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() {
const files = [];
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) {
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, from, to, inLanguage) {
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, language) {
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, language) {
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) {
const hybridObject = getTypeAs(type, HybridObjectType);
return getHybridObjectName(hybridObject.hybridObjectName);
}