UNPKG

nitro-codegen

Version:

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

437 lines (407 loc) 16.6 kB
import { SwiftCxxBridgedType } from './SwiftCxxBridgedType.js'; import { indent } from '../../utils.js'; import { createFileMetadataString, escapeCppName, isNotDuplicate, } from '../helpers.js'; import { getHybridObjectName } from '../getHybridObjectName.js'; import { getForwardDeclaration } from '../c++/getForwardDeclaration.js'; import { NitroConfig } from '../../config/NitroConfig.js'; import { includeHeader } from '../c++/includeNitroHeader.js'; import { getUmbrellaHeaderName } from '../../autolinking/ios/createSwiftUmbrellaHeader.js'; import { HybridObjectType } from '../types/HybridObjectType.js'; import { addKnownType } from '../createType.js'; import { ResultWrappingType } from '../types/ResultWrappingType.js'; export function getBridgeNamespace() { return NitroConfig.getCxxNamespace('swift', 'bridge', 'swift'); } /** * Creates a Swift class that bridges Swift over to C++. * We need this because not all Swift types are accessible in C++, and vice versa. * * For example, Enums need to be converted to Int32 (because of a Swift compiler bug), * Promise<..> has to be converted to a Promise<..>, exceptions have to be handled * via custom Result types, etc.. */ export function createSwiftHybridObjectCxxBridge(spec) { const name = getHybridObjectName(spec.name); const moduleName = NitroConfig.getIosModuleName(); const propertiesBridge = spec.properties.map((p) => getPropertyForwardImplementation(p)); const methodsBridge = spec.methods.map((m) => getMethodForwardImplementation(m)); const baseClasses = spec.baseTypes.map((base) => { const baseName = getHybridObjectName(base.name); return baseName.HybridTSpecCxx; }); const hasBase = baseClasses.length > 0; if (spec.isHybridView && !hasBase) { methodsBridge.push(` public final func getView() -> UnsafeMutableRawPointer { return Unmanaged.passRetained(__implementation.view).toOpaque() } `.trim(), ` public final func beforeUpdate() { __implementation.beforeUpdate() } `.trim(), ` public final func afterUpdate() { __implementation.afterUpdate() } `.trim()); } const hybridObject = new HybridObjectType(spec); const bridgedType = new SwiftCxxBridgedType(hybridObject); const bridge = bridgedType.getRequiredBridge(); if (bridge == null) throw new Error(`HybridObject Type should have a bridge!`); const weakifyBridge = bridge.dependencies.find((d) => d.funcName.startsWith('weakify')); if (weakifyBridge == null) throw new Error(`HybridObject ${spec.name} does not have a weakify_..() bridge!`); const cppWeakPtrName = escapeCppName(hybridObject.getCode('c++', 'weak')); const baseGetCxxPartOverrides = spec.baseTypes.map((base) => { const baseHybridObject = new HybridObjectType(base); const bridgedBase = new SwiftCxxBridgedType(baseHybridObject); const baseBridge = bridgedBase.getRequiredBridge(); if (baseBridge == null) throw new Error(`HybridObject ${base.name}'s bridge cannot be null!`); const upcastBridge = bridge.dependencies.find((b) => b.funcName.includes(escapeCppName(spec.name)) && b.funcName.includes(escapeCppName(base.name))); if (upcastBridge == null) throw new Error(`HybridObject ${spec.name}'s upcast-bridge cannot be found! ${JSON.stringify(baseBridge)}`); return ` public override func getCxxPart() -> bridge.${baseBridge.specializationName} { let ownCxxPart: bridge.${bridge.specializationName} = getCxxPart() return bridge.${upcastBridge.funcName}(ownCxxPart) }`.trim(); }); const swiftCxxWrapperCode = ` ${createFileMetadataString(`${name.HybridTSpecCxx}.swift`)} import Foundation import NitroModules /** * A class implementation that bridges ${name.HybridTSpec} over to C++. * In C++, we cannot use Swift protocols - so we need to wrap it in a class to make it strongly defined. * * Also, some Swift types need to be bridged with special handling: * - Enums need to be wrapped in Structs, otherwise they cannot be accessed bi-directionally (Swift bug: https://github.com/swiftlang/swift/issues/75330) * - Other HybridObjects need to be wrapped/unwrapped from the Swift TCxx wrapper * - Throwing methods need to be wrapped with a Result<T, Error> type, as exceptions cannot be propagated to C++ */ ${hasBase ? `public class ${name.HybridTSpecCxx} : ${baseClasses.join(', ')}` : `public class ${name.HybridTSpecCxx}`} { /** * The Swift <> C++ bridge's namespace (\`${NitroConfig.getCxxNamespace('c++', 'bridge', 'swift')}\`) * from \`${moduleName}-Swift-Cxx-Bridge.hpp\`. * This contains specialized C++ templates, and C++ helper functions that can be accessed from Swift. */ public typealias bridge = ${getBridgeNamespace()} /** * Holds an instance of the \`${name.HybridTSpec}\` Swift protocol. */ private var __implementation: any ${name.HybridTSpec} /** * Holds a weak pointer to the C++ class that wraps the Swift class. */ private var __cxxPart: bridge.${cppWeakPtrName} /** * Create a new \`${name.HybridTSpecCxx}\` that wraps the given \`${name.HybridTSpec}\`. * All properties and methods bridge to C++ types. */ public init(_ implementation: any ${name.HybridTSpec}) { self.__implementation = implementation self.__cxxPart = .init() ${hasBase ? 'super.init(implementation)' : '/* no base class */'} } /** * Get the actual \`${name.HybridTSpec}\` instance this class wraps. */ @inline(__always) public func get${name.HybridTSpec}() -> any ${name.HybridTSpec} { return __implementation } /** * Casts this instance to a retained unsafe raw pointer. * This acquires one additional strong reference on the object! */ public ${hasBase ? 'override func' : 'func'} toUnsafe() -> UnsafeMutableRawPointer { return Unmanaged.passRetained(self).toOpaque() } /** * Casts an unsafe pointer to a \`${name.HybridTSpecCxx}\`. * The pointer has to be a retained opaque \`Unmanaged<${name.HybridTSpecCxx}>\`. * This removes one strong reference from the object! */ public ${hasBase ? 'override class func' : 'class func'} fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> ${name.HybridTSpecCxx} { return Unmanaged<${name.HybridTSpecCxx}>.fromOpaque(pointer).takeRetainedValue() } /** * Gets (or creates) the C++ part of this Hybrid Object. * The C++ part is a \`${bridge.cxxType}\`. */ public func getCxxPart() -> bridge.${bridge.specializationName} { let cachedCxxPart = self.__cxxPart.lock() if cachedCxxPart.__convertToBool() { return cachedCxxPart } else { let newCxxPart = bridge.${bridge.funcName}(self.toUnsafe()) __cxxPart = bridge.${weakifyBridge.funcName}(newCxxPart) return newCxxPart } } ${indent(baseGetCxxPartOverrides.join('\n'), ' ')} /** * Get the memory size of the Swift class (plus size of any other allocations) * so the JS VM can properly track it and garbage-collect the JS object if needed. */ @inline(__always) public ${hasBase ? 'override var' : 'var'} memorySize: Int { return MemoryHelper.getSizeOf(self.__implementation) + self.__implementation.memorySize } // Properties ${indent(propertiesBridge.join('\n\n'), ' ')} // Methods ${indent(methodsBridge.join('\n\n'), ' ')} } `; const cppProperties = spec.properties .map((p) => { const bridged = new SwiftCxxBridgedType(p.type); let getter; let setter; const getterName = p.getGetterName('swift'); const setterName = p.getSetterName('swift'); if (bridged.needsSpecialHandling) { // we need custom C++ -> Swift conversion code getter = ` auto __result = _swiftPart.${getterName}(); return ${bridged.parseFromSwiftToCpp('__result', 'c++')}; `; setter = `_swiftPart.${setterName}(${bridged.parseFromCppToSwift(p.name, 'c++')});`; } else { // just forward value directly getter = `return _swiftPart.${getterName}();`; setter = `_swiftPart.${setterName}(std::forward<decltype(${p.name})>(${p.name}));`; } return p.getCode('c++', { inline: true, override: true, noexcept: true }, { getter: getter.trim(), setter: setter.trim(), }); }) .join('\n'); const cppMethods = spec.methods .map((m) => { const params = m.parameters .map((p) => { const bridged = new SwiftCxxBridgedType(p.type); if (bridged.needsSpecialHandling) { // we need custom C++ -> Swift conversion code return bridged.parseFromCppToSwift(p.name, 'c++'); } else { // just forward value directly return `std::forward<decltype(${p.name})>(${p.name})`; } }) .join(', '); const bridgedReturnType = new SwiftCxxBridgedType(m.returnType, true); const hasResult = m.returnType.kind !== 'void'; let body; if (hasResult) { // func returns something body = ` auto __result = _swiftPart.${m.name}(${params}); if (__result.hasError()) [[unlikely]] { std::rethrow_exception(__result.error()); } auto __value = std::move(__result.value()); return ${bridgedReturnType.parseFromSwiftToCpp('__value', 'c++')}; `.trim(); } else { // void func body = ` auto __result = _swiftPart.${m.name}(${params}); if (__result.hasError()) [[unlikely]] { std::rethrow_exception(__result.error()); } `.trim(); } return m.getCode('c++', { inline: true, override: true }, body); }) .join('\n'); const allBridgedTypes = [ ...spec.properties.flatMap((p) => new SwiftCxxBridgedType(p.type)), ...spec.methods.flatMap((m) => { const bridgedReturn = new SwiftCxxBridgedType(m.returnType); const bridgedParams = m.parameters.map((p) => new SwiftCxxBridgedType(p.type)); return [bridgedReturn, ...bridgedParams]; }), ]; const cxxNamespace = NitroConfig.getCxxNamespace('c++'); const iosModuleName = NitroConfig.getIosModuleName(); const extraImports = allBridgedTypes.flatMap((b) => b.getRequiredImports()); const cppBaseClasses = [`public virtual ${name.HybridTSpec}`]; const cppBaseCtorCalls = [`HybridObject(${name.HybridTSpec}::TAG)`]; for (const base of spec.baseTypes) { const baseName = getHybridObjectName(base.name); cppBaseClasses.push(`public virtual ${baseName.HybridTSpecSwift}`); cppBaseCtorCalls.push(`${baseName.HybridTSpecSwift}(swiftPart)`); extraImports.push({ language: 'c++', name: `${baseName.HybridTSpecSwift}.hpp`, space: 'user', forwardDeclaration: getForwardDeclaration('class', baseName.HybridTSpecSwift, cxxNamespace), }); } const extraForwardDeclarations = extraImports .map((i) => i.forwardDeclaration) .filter((v) => v != null) .filter(isNotDuplicate); const extraIncludes = extraImports .map((i) => includeHeader(i)) .filter(isNotDuplicate); // TODO: Remove forward declaration once Swift fixes the wrong order in generated -Swift.h headers! const cppHybridObjectCode = ` ${createFileMetadataString(`${name.HybridTSpecSwift}.hpp`)} #pragma once #include "${name.HybridTSpec}.hpp" ${getForwardDeclaration('class', name.HybridTSpecCxx, iosModuleName)} ${extraForwardDeclarations.join('\n')} ${extraIncludes.join('\n')} #include "${getUmbrellaHeaderName()}" namespace ${cxxNamespace} { /** * The C++ part of ${name.HybridTSpecCxx}.swift. * * ${name.HybridTSpecSwift} (C++) accesses ${name.HybridTSpecCxx} (Swift), and might * contain some additional bridging code for C++ <> Swift interop. * * Since this obviously introduces an overhead, I hope at some point in * the future, ${name.HybridTSpecCxx} can directly inherit from the C++ class ${name.HybridTSpec} * to simplify the whole structure and memory management. */ class ${name.HybridTSpecSwift}: ${cppBaseClasses.join(', ')} { public: // Constructor from a Swift instance explicit ${name.HybridTSpecSwift}(const ${iosModuleName}::${name.HybridTSpecCxx}& swiftPart): ${indent(cppBaseCtorCalls.join(',\n'), ' ')}, _swiftPart(swiftPart) { } public: // Get the Swift part inline ${iosModuleName}::${name.HybridTSpecCxx}& getSwiftPart() noexcept { return _swiftPart; } public: // Get memory pressure inline size_t getExternalMemorySize() noexcept override { return _swiftPart.getMemorySize(); } public: // Properties ${indent(cppProperties, ' ')} public: // Methods ${indent(cppMethods, ' ')} private: ${iosModuleName}::${name.HybridTSpecCxx} _swiftPart; }; } // namespace ${cxxNamespace} `; const cppHybridObjectCodeCpp = ` ${createFileMetadataString(`${name.HybridTSpecSwift}.cpp`)} #include "${name.HybridTSpecSwift}.hpp" namespace ${cxxNamespace} { } // namespace ${cxxNamespace} `; const files = []; files.push(...allBridgedTypes.flatMap((b) => b.getExtraFiles())); files.push({ content: swiftCxxWrapperCode, language: 'swift', name: `${name.HybridTSpecCxx}.swift`, subdirectory: [], platform: 'ios', }); files.push({ content: cppHybridObjectCode, language: 'c++', name: `${name.HybridTSpecSwift}.hpp`, subdirectory: [], platform: 'ios', }); files.push({ content: cppHybridObjectCodeCpp, language: 'c++', name: `${name.HybridTSpecSwift}.cpp`, subdirectory: [], platform: 'ios', }); return files; } function getPropertyForwardImplementation(property) { const bridgedType = new SwiftCxxBridgedType(property.type); const convertToCpp = bridgedType.parseFromSwiftToCpp(`self.__implementation.${property.name}`, 'swift'); const convertFromCpp = bridgedType.parseFromCppToSwift('newValue', 'swift'); const getter = ` @inline(__always) get { return ${indent(convertToCpp, ' ')} } `.trim(); const setter = ` @inline(__always) set { self.__implementation.${property.name} = ${indent(convertFromCpp, ' ')} } `.trim(); const body = [getter]; if (!property.isReadonly) { body.push(setter); } const code = ` public final var ${property.name}: ${bridgedType.getTypeCode('swift')} { ${indent(body.join('\n'), ' ')} } `; return code.trim(); } function getMethodForwardImplementation(method) { // wrapped return in a std::expected const resultType = new ResultWrappingType(method.returnType); addKnownType(`expected_${resultType.getCode('c++')}`, resultType, 'swift'); const bridgedResultType = new SwiftCxxBridgedType(resultType, true); const resultBridge = bridgedResultType.getRequiredBridge(); if (resultBridge == null) throw new Error(`Result type (${bridgedResultType.getTypeCode('c++')}) does not have a bridge!`); const bridgedErrorType = new SwiftCxxBridgedType(resultType.error, true); const returnType = new SwiftCxxBridgedType(method.returnType, true); const params = method.parameters.map((p) => { const bridgedType = new SwiftCxxBridgedType(p.type); return `${p.name}: ${bridgedType.getTypeCode('swift')}`; }); const passParams = method.parameters.map((p) => { const bridgedType = new SwiftCxxBridgedType(p.type); return `${p.name}: ${bridgedType.parseFromCppToSwift(p.name, 'swift')}`; }); let body; if (returnType.hasType) { body = ` let __result = try self.__implementation.${method.name}(${passParams.join(', ')}) let __resultCpp = ${returnType.parseFromSwiftToCpp('__result', 'swift')} return bridge.${resultBridge.funcName}(__resultCpp) `.trim(); } else { body = ` try self.__implementation.${method.name}(${passParams.join(', ')}) return bridge.${resultBridge.funcName}() `.trim(); } return ` @inline(__always) public final func ${method.name}(${params.join(', ')}) -> ${bridgedResultType.getTypeCode('swift')} { do { ${indent(body, ' ')} } catch (let __error) { let __exceptionPtr = ${indent(bridgedErrorType.parseFromSwiftToCpp('__error', 'swift'), ' ')} return bridge.${resultBridge.funcName}(__exceptionPtr) } } `.trim(); }