nitro-codegen
Version:
The code-generator for react-native-nitro-modules.
560 lines (559 loc) • 19.1 kB
JavaScript
import { escapeCppName, toReferenceType } from '../helpers.js';
import { VariantType } from '../types/VariantType.js';
import { ArrayType } from '../types/ArrayType.js';
import { FunctionType } from '../types/FunctionType.js';
import { getTypeAs } from '../types/getTypeAs.js';
import { OptionalType } from '../types/OptionalType.js';
import { RecordType } from '../types/RecordType.js';
import { TupleType } from '../types/TupleType.js';
import { escapeComments, indent } from '../../utils.js';
import { PromiseType } from '../types/PromiseType.js';
import { SwiftCxxBridgedType } from './SwiftCxxBridgedType.js';
import { HybridObjectType } from '../types/HybridObjectType.js';
import { getHybridObjectName } from '../getHybridObjectName.js';
import { NitroConfig } from '../../config/NitroConfig.js';
import { getUmbrellaHeaderName } from '../../autolinking/ios/createSwiftUmbrellaHeader.js';
import { VoidType } from '../types/VoidType.js';
import { NamedWrappingType } from '../types/NamedWrappingType.js';
import { ErrorType } from '../types/ErrorType.js';
import { ResultWrappingType } from '../types/ResultWrappingType.js';
export function createSwiftCxxHelpers(type) {
switch (type.kind) {
case 'hybrid-object':
return createCxxHybridObjectSwiftHelper(getTypeAs(type, HybridObjectType));
case 'optional':
return createCxxOptionalSwiftHelper(getTypeAs(type, OptionalType));
case 'array':
return createCxxVectorSwiftHelper(getTypeAs(type, ArrayType));
case 'record':
return createCxxUnorderedMapSwiftHelper(getTypeAs(type, RecordType));
case 'function':
return createCxxFunctionSwiftHelper(getTypeAs(type, FunctionType));
case 'variant':
return createCxxVariantSwiftHelper(getTypeAs(type, VariantType));
case 'tuple':
return createCxxTupleSwiftHelper(getTypeAs(type, TupleType));
case 'promise':
return createCxxPromiseSwiftHelper(getTypeAs(type, PromiseType));
case 'result-wrapper':
return createCxxResultWrapperSwiftHelper(getTypeAs(type, ResultWrappingType));
default:
return undefined;
}
}
/**
* Creates a C++ `create_HybridTSpecSwift(value)` function that can be called from Swift.
*/
function createCxxHybridObjectSwiftHelper(type) {
const actualType = type.getCode('c++');
const modulename = NitroConfig.getIosModuleName();
const { HybridTSpecCxx, HybridTSpecSwift, HybridTSpec } = getHybridObjectName(type.hybridObjectName);
const swiftWrappingType = NitroConfig.getCxxNamespace('c++', HybridTSpecSwift);
const swiftPartType = `${modulename}::${HybridTSpecCxx}`;
const name = escapeCppName(actualType);
const upcastHelpers = type.baseTypes.map((base) => createCxxUpcastHelper(base, type));
return {
cxxType: actualType,
funcName: `create_${name}`,
specializationName: name,
cxxHeader: {
code: `
/**
* Specialized version of \`${escapeComments(actualType)}\`.
*/
using ${name} = ${actualType};
${actualType} create_${name}(void* _Nonnull swiftUnsafePointer);
void* _Nonnull get_${name}(${name} cppType);
`.trim(),
requiredIncludes: type.getRequiredImports(),
},
cxxImplementation: {
code: `
${actualType} create_${name}(void* _Nonnull swiftUnsafePointer) {
${swiftPartType} swiftPart = ${swiftPartType}::fromUnsafe(swiftUnsafePointer);
return std::make_shared<${swiftWrappingType}>(swiftPart);
}
void* _Nonnull get_${name}(${name} cppType) {
std::shared_ptr<${swiftWrappingType}> swiftWrapper = std::dynamic_pointer_cast<${swiftWrappingType}>(cppType);
#ifdef NITRO_DEBUG
if (swiftWrapper == nullptr) [[unlikely]] {
throw std::runtime_error("Class \\"${HybridTSpec}\\" is not implemented in Swift!");
}
#endif
${swiftPartType}& swiftPart = swiftWrapper->getSwiftPart();
return swiftPart.toUnsafe();
}
`.trim(),
requiredIncludes: [
{
language: 'c++',
// Hybrid Object Swift C++ class wrapper
name: `${HybridTSpecSwift}.hpp`,
space: 'user',
},
{
language: 'c++',
// Swift umbrella header
name: getUmbrellaHeaderName(),
space: 'user',
},
],
},
dependencies: [...upcastHelpers, createCxxWeakPtrHelper(type)],
};
}
function createCxxUpcastHelper(baseType, childType) {
const cppBaseType = baseType.getCode('c++');
const cppChildType = childType.getCode('c++');
const funcName = escapeCppName(`upcast_${childType.hybridObjectName}_to_${baseType.hybridObjectName}`);
return {
cxxType: cppBaseType,
funcName: funcName,
specializationName: funcName,
cxxHeader: {
code: `
inline ${cppBaseType} ${funcName}(${cppChildType} child) { return child; }
`.trim(),
requiredIncludes: [],
},
dependencies: [],
};
}
function createCxxWeakPtrHelper(type) {
const actualType = type.getCode('c++', 'weak');
const specializationName = escapeCppName(actualType);
const funcName = `weakify_${escapeCppName(type.getCode('c++'))}`;
return {
cxxType: actualType,
funcName: funcName,
specializationName: specializationName,
cxxHeader: {
code: `
using ${specializationName} = ${actualType};
inline ${specializationName} ${funcName}(const ${type.getCode('c++', 'strong')}& strong) { return strong; }
`.trim(),
requiredIncludes: [],
},
dependencies: [],
};
}
/**
* Creates a C++ `create_optional<T>(value)` function that can be called from Swift.
*/
function createCxxOptionalSwiftHelper(type) {
const actualType = type.getCode('c++');
const wrappedBridge = new SwiftCxxBridgedType(type.wrappingType, true);
const name = escapeCppName(actualType);
return {
cxxType: actualType,
funcName: `create_${name}`,
specializationName: name,
cxxHeader: {
code: `
/**
* Specialized version of \`${escapeComments(actualType)}\`.
*/
using ${name} = ${actualType};
inline ${actualType} create_${name}(const ${wrappedBridge.getTypeCode('c++')}& value) {
return ${actualType}(${indent(wrappedBridge.parseFromSwiftToCpp('value', 'c++'), ' ')});
}
`.trim(),
requiredIncludes: [
{
name: 'optional',
space: 'system',
language: 'c++',
},
...wrappedBridge.getRequiredImports(),
],
},
dependencies: [],
};
}
/**
* Creates a C++ `create_vector_T<T>(size)` function that can be called from Swift.
*/
function createCxxVectorSwiftHelper(type) {
const actualType = type.getCode('c++');
const bridgedType = new SwiftCxxBridgedType(type);
const name = escapeCppName(actualType);
return {
cxxType: actualType,
funcName: `create_${name}`,
specializationName: name,
cxxHeader: {
code: `
/**
* Specialized version of \`${escapeComments(actualType)}\`.
*/
using ${name} = ${actualType};
inline ${actualType} create_${name}(size_t size) {
${actualType} vector;
vector.reserve(size);
return vector;
}
`.trim(),
requiredIncludes: [
{
name: 'vector',
space: 'system',
language: 'c++',
},
...bridgedType.getRequiredImports(),
],
},
dependencies: [],
};
}
/**
* Creates a C++ `makeUnorderedMap<T>(size)` function that can be called from Swift.
*/
function createCxxUnorderedMapSwiftHelper(type) {
const actualType = type.getCode('c++');
const bridgedType = new SwiftCxxBridgedType(type);
const name = escapeCppName(actualType);
const keyType = type.keyType.getCode('c++');
const valueType = type.valueType.getCode('c++');
return {
cxxType: actualType,
funcName: `create_${name}`,
specializationName: name,
cxxHeader: {
code: `
/**
* Specialized version of \`${escapeComments(actualType)}\`.
*/
using ${name} = ${actualType};
inline ${actualType} create_${name}(size_t size) {
${actualType} map;
map.reserve(size);
return map;
}
inline std::vector<${keyType}> get_${name}_keys(const ${name}& map) {
std::vector<${keyType}> keys;
keys.reserve(map.size());
for (const auto& entry : map) {
keys.push_back(entry.first);
}
return keys;
}
inline void emplace_${name}(${name}& map, const ${keyType}& key, const ${valueType}& value) {
map.emplace(key, value);
}
`.trim(),
requiredIncludes: [
{
name: 'unordered_map',
space: 'system',
language: 'c++',
},
...bridgedType.getRequiredImports(),
],
},
dependencies: [],
};
}
/**
* Creates a C++ `Func_XXXXX` specialization that can be used from Swift.
*/
function createCxxFunctionSwiftHelper(type) {
const actualType = type.getCode('c++');
const bridgedType = new SwiftCxxBridgedType(type);
const returnBridge = new SwiftCxxBridgedType(type.returnType);
const paramsSignature = type.parameters.map((p) => {
if (p.canBePassedByReference) {
return `${toReferenceType(p.getCode('c++'))} ${p.escapedName}`;
}
else {
return `${p.getCode('c++')} ${p.escapedName}`;
}
});
const paramsForward = type.parameters.map((p) => {
const bridge = new SwiftCxxBridgedType(p);
return bridge.parseFromCppToSwift(p.escapedName, 'c++');
});
const name = type.specializationName;
const wrapperName = `${name}_Wrapper`;
const swiftClassName = `${NitroConfig.getIosModuleName()}::${type.specializationName}`;
const callParamsForward = type.parameters.map((p) => {
const bridge = new SwiftCxxBridgedType(p);
return bridge.parseFromSwiftToCpp(p.escapedName, 'c++');
});
const callFuncReturnType = returnBridge.getTypeCode('c++');
const callCppFuncParamsSignature = type.parameters.map((p) => {
const bridge = new SwiftCxxBridgedType(p);
const cppType = bridge.getTypeCode('c++');
return `${cppType} ${p.escapedName}`;
});
let callCppFuncBody;
if (returnBridge.hasType) {
callCppFuncBody = `
auto __result = _function->operator()(${callParamsForward.join(', ')});
return ${returnBridge.parseFromCppToSwift('__result', 'c++')};
`.trim();
}
else {
callCppFuncBody = `_function->operator()(${callParamsForward.join(', ')});`;
}
let body;
if (type.returnType.kind === 'void') {
body = `
swiftClosure.call(${paramsForward.join(', ')});
`.trim();
}
else {
body = `
auto __result = swiftClosure.call(${paramsForward.join(', ')});
return ${returnBridge.parseFromSwiftToCpp('__result', 'c++')};
`.trim();
}
return {
cxxType: actualType,
funcName: `create_${name}`,
specializationName: name,
cxxHeader: {
code: `
/**
* Specialized version of \`${type.getCode('c++', false)}\`.
*/
using ${name} = ${actualType};
/**
* Wrapper class for a \`${escapeComments(actualType)}\`, this can be used from Swift.
*/
class ${wrapperName} final {
public:
explicit ${wrapperName}(${actualType}&& func): _function(std::make_shared<${actualType}>(std::move(func))) {}
inline ${callFuncReturnType} call(${callCppFuncParamsSignature.join(', ')}) const {
${indent(callCppFuncBody, ' ')}
}
private:
std::shared_ptr<${actualType}> _function;
};
${name} create_${name}(void* _Nonnull swiftClosureWrapper);
inline ${wrapperName} wrap_${name}(${name} value) {
return ${wrapperName}(std::move(value));
}
`.trim(),
requiredIncludes: [
{
name: 'functional',
space: 'system',
language: 'c++',
},
{
name: 'memory',
space: 'system',
language: 'c++',
},
...bridgedType.getRequiredImports(),
],
},
cxxImplementation: {
code: `
${name} create_${name}(void* _Nonnull swiftClosureWrapper) {
auto swiftClosure = ${swiftClassName}::fromUnsafe(swiftClosureWrapper);
return [swiftClosure = std::move(swiftClosure)](${paramsSignature.join(', ')}) mutable -> ${type.returnType.getCode('c++')} {
${indent(body, ' ')}
};
}
`.trim(),
requiredIncludes: [
{
language: 'c++',
// Swift umbrella header
name: getUmbrellaHeaderName(),
space: 'user',
},
],
},
dependencies: [],
};
}
/**
* Creates multiple C++ `create_variant_A_B_C<A, B, C>(A...)` functions that can be called from Swift.
*/
function createCxxVariantSwiftHelper(type) {
const actualType = type.getCode('c++');
const bridgedType = new SwiftCxxBridgedType(type);
const name = escapeCppName(actualType);
const createFunctions = type.variants.map((t) => {
const param = t.canBePassedByReference
? toReferenceType(t.getCode('c++'))
: t.getCode('c++');
return `
inline ${name} create_${name}(${param} value) {
return ${name}(value);
}
`.trim();
});
const getFunctions = type.variants.map((t, i) => {
return `
inline ${t.getCode('c++')} get_${i}() const {
return std::get<${i}>(variant);
}`.trim();
});
return {
cxxType: actualType,
funcName: `create_${name}`,
specializationName: name,
cxxHeader: {
code: `
/**
* Wrapper struct for \`${escapeComments(actualType)}\`.
* std::variant cannot be used in Swift because of a Swift bug.
* Not even specializing it works. So we create a wrapper struct.
*/
struct ${name} {
${actualType} variant;
${name}(${actualType} variant): variant(variant) { }
operator ${actualType}() const {
return variant;
}
inline size_t index() const {
return variant.index();
}
${indent(getFunctions.join('\n'), ' ')}
};
${createFunctions.join('\n')}
`.trim(),
requiredIncludes: [
{
name: 'variant',
space: 'system',
language: 'c++',
},
...bridgedType.getRequiredImports(),
],
},
dependencies: [],
};
}
/**
* Creates a C++ `create_tuple_A_B_C<A, B, C>(A, B, C)` function that can be called from Swift.
*/
function createCxxTupleSwiftHelper(type) {
const actualType = type.getCode('c++');
const bridgedType = new SwiftCxxBridgedType(type);
const name = escapeCppName(actualType);
const typesSignature = type.itemTypes
.map((t, i) => {
const code = t.getCode('c++');
return `${t.canBePassedByReference ? toReferenceType(code) : code} arg${i}`;
})
.join(', ');
const typesForward = type.itemTypes.map((_t, i) => `arg${i}`).join(', ');
return {
cxxType: actualType,
funcName: `create_${name}`,
specializationName: name,
cxxHeader: {
code: `
/**
* Specialized version of \`${escapeComments(actualType)}\`.
*/
using ${name} = ${actualType};
inline ${actualType} create_${name}(${typesSignature}) {
return ${actualType} { ${typesForward} };
}
`.trim(),
requiredIncludes: [
{
name: 'tuple',
space: 'system',
language: 'c++',
},
...bridgedType.getRequiredImports(),
],
},
dependencies: [],
};
}
/**
* Create a C++ `create_result` function that can be called from Swift to create a `Result<T>`.
*/
function createCxxResultWrapperSwiftHelper(type) {
const actualType = type.getCode('c++');
const name = escapeCppName(type.getCode('c++'));
const funcName = `create_${name}`;
const functions = [];
if (type.result.kind === 'void') {
functions.push(`
inline ${name} ${funcName}() {
return ${actualType}::withValue();
}`.trim());
}
else {
const typeParam = type.result.canBePassedByReference
? `const ${type.result.getCode('c++')}&`
: type.result.getCode('c++');
functions.push(`
inline ${name} ${funcName}(${typeParam} value) {
return ${actualType}::withValue(${type.result.canBePassedByReference ? 'value' : 'std::move(value)'});
}`.trim());
}
functions.push(`
inline ${name} ${funcName}(const ${type.error.getCode('c++')}& error) {
return ${actualType}::withError(error);
}`.trim());
return {
cxxType: actualType,
specializationName: name,
funcName: funcName,
cxxHeader: {
code: `
using ${name} = ${actualType};
${functions.join('\n')}
`.trim(),
requiredIncludes: type.getRequiredImports(),
},
dependencies: [],
};
}
/**
* Creates a C++ `create_promise_T()` function that can be called from Swift to create a `std::shared_ptr<Promise<T>>`.
*/
function createCxxPromiseSwiftHelper(type) {
const resultingType = type.resultingType.getCode('c++');
const bridgedType = new SwiftCxxBridgedType(type);
const actualType = `std::shared_ptr<Promise<${resultingType}>>`;
const resolverArgs = [];
if (type.resultingType.kind !== 'void') {
resolverArgs.push(new NamedWrappingType('result', type.resultingType));
}
const resolveFunction = new FunctionType(new VoidType(), resolverArgs);
const rejectFunction = new FunctionType(new VoidType(), [
new NamedWrappingType('error', new ErrorType()),
]);
const name = escapeCppName(actualType);
return {
cxxType: actualType,
funcName: `create_${name}`,
specializationName: name,
cxxHeader: {
code: `
/**
* Specialized version of \`${escapeComments(actualType)}\`.
*/
using ${name} = ${actualType};
inline ${actualType} create_${name}() {
return Promise<${resultingType}>::create();
}
inline PromiseHolder<${resultingType}> wrap_${name}(${actualType} promise) {
return PromiseHolder<${resultingType}>(std::move(promise));
}
`.trim(),
requiredIncludes: [
{
name: 'NitroModules/PromiseHolder.hpp',
space: 'system',
language: 'c++',
},
...bridgedType.getRequiredImports(),
],
},
dependencies: [
createCxxFunctionSwiftHelper(resolveFunction),
createCxxFunctionSwiftHelper(rejectFunction),
],
};
}