From 6eecbcd0c3c6c62f3d7661221844fde71e62987b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 27 Oct 2026 14:53:36 +0100 Subject: [PATCH 1/5] fix(swift): bridge callback struct fields through opaque storage --- .../src/syntax/swift/SwiftMethodHelpers.ts | 49 +++--- .../nitrogen/src/syntax/swift/SwiftStruct.ts | 144 ++++++++++++++---- .../src/syntax/swift/SwiftStructHelpers.ts | 64 ++++++++ .../apple/c++/JsStyleStruct-Swift.cpp | 10 +- .../generated/apple/swift/JsStyleStruct.swift | 10 +- 5 files changed, 222 insertions(+), 55 deletions(-) create mode 100644 packages/nitrogen/src/syntax/swift/SwiftStructHelpers.ts diff --git a/packages/nitrogen/src/syntax/swift/SwiftMethodHelpers.ts b/packages/nitrogen/src/syntax/swift/SwiftMethodHelpers.ts index b9c533c173..851a965f7d 100644 --- a/packages/nitrogen/src/syntax/swift/SwiftMethodHelpers.ts +++ b/packages/nitrogen/src/syntax/swift/SwiftMethodHelpers.ts @@ -63,16 +63,39 @@ export interface SwiftMethodInfo { resultType: SwiftResultType } +export function getSwiftBridgeParameterType(type: SwiftType): SwiftType { + if (type.requiresOpaqueCxxBridge) { + return new SwiftCxxBorrowedPointerType(type) + } + return type +} + +interface SwiftBridgeReturnTypeOptions { + type: SwiftType + isThrowing: boolean +} + +export function getSwiftBridgeReturnType({ type, isThrowing }: SwiftBridgeReturnTypeOptions): SwiftType { + if (type.requiresOpaqueCxxBridge) { + return new SwiftCxxOutPointerType(type) + } + if (isThrowing && type.isReferenceType) { + // A throwing class result crosses the boundary as an owned opaque pointer. + return new SwiftRetainedPointerType(type) + } + if (isThrowing && !type.canImportFromCpp) { + // Swift cannot import Result while a Swift-native T is incomplete. + // Return Result alongside its value buffer; Core constructs Result. + return new SwiftOutPointerType(type) + } + return type +} + export function prepareSwiftMethod(nativeMethod: Method, isThrowing: boolean, config: NitroConfig = NitroConfig.current): SwiftMethodInfo { const returnType = wrapSwiftType(nativeMethod.returnType, config) const parameters = nativeMethod.parameters.map((parameter) => { const swiftType = wrapSwiftType(parameter.type, config) - let exportedType: SwiftType - if (swiftType.requiresOpaqueCxxBridge) { - exportedType = new SwiftCxxBorrowedPointerType(swiftType) - } else { - exportedType = swiftType - } + const exportedType = getSwiftBridgeParameterType(swiftType) return { native: parameter, swift: new Parameter(parameter.name, swiftType), @@ -80,19 +103,7 @@ export function prepareSwiftMethod(nativeMethod: Method, isThrowing: boolean, co exported: { name: parameter.type.escapedName, type: exportedType }, } }) - let exportedReturnType: SwiftType - if (returnType.requiresOpaqueCxxBridge) { - exportedReturnType = new SwiftCxxOutPointerType(returnType) - } else if (isThrowing && returnType.isReferenceType) { - // A throwing class result crosses the boundary as an owned opaque pointer. - exportedReturnType = new SwiftRetainedPointerType(returnType) - } else if (isThrowing && !returnType.canImportFromCpp) { - // Swift cannot import Result while a Swift-native T is incomplete. - // Return Result alongside its value buffer; Core constructs Result. - exportedReturnType = new SwiftOutPointerType(returnType) - } else { - exportedReturnType = returnType - } + const exportedReturnType = getSwiftBridgeReturnType({ type: returnType, isThrowing: isThrowing }) const resultType = getSwiftResultType(exportedReturnType, config) return { nativeMethod: nativeMethod, diff --git a/packages/nitrogen/src/syntax/swift/SwiftStruct.ts b/packages/nitrogen/src/syntax/swift/SwiftStruct.ts index fbd21aac75..5e91c87ce7 100644 --- a/packages/nitrogen/src/syntax/swift/SwiftStruct.ts +++ b/packages/nitrogen/src/syntax/swift/SwiftStruct.ts @@ -5,13 +5,18 @@ import { getTypeAs } from '../types/getTypeAs.js' import type { Type } from '../types/Type.js' import { capitalizeName, indent } from '../../utils.js' import { createFileMetadataString } from '../helpers.js' -import type { SourceFile } from '../SourceFile.js' +import type { SourceFile, SourceImport } from '../SourceFile.js' import type { SwiftStructType } from './types/SwiftStructType.js' import { getImportsCode } from '../getImportCode.js' import { getProtocolConformanceImports, getSwiftImports } from './getSwiftImports.js' +import { SWIFT_RESULT_STORAGE } from './SwiftMethodHelpers.js' +import { createCppStructGetter, createCppStructInitializer, getSwiftStructPropertyBridges } from './SwiftStructHelpers.js' +import { SwiftCxxOutPointerType } from './types/SwiftCxxOutPointerType.js' +import { SwiftCxxBorrowedPointerType } from './types/SwiftCxxBorrowedPointerType.js' export function createSwiftStructBridge(swiftType: SwiftStructType): SourceFile[] { const structType = swiftType.type + const properties = getSwiftStructPropertyBridges(swiftType) const conformances = swiftType.getProtocolConformances() const protocolNames = conformances.map((conformance) => conformance.name) const conformancePrefix = protocolNames.length > 0 ? ': ' : '' @@ -20,16 +25,51 @@ export function createSwiftStructBridge(swiftType: SwiftStructType): SourceFile[ .map((p) => `${p.escapedName}: ${p.getCode('swift', { context: 'property' })}`) .join(', ') const swiftPropertyInitializers = structType.properties.map((p) => `self.${p.escapedName} = ${p.escapedName}`) - const swiftBridgeInitializerParameters = swiftType.properties - .map((p) => `_ ${p.escapedName}: ${p.getCode('swift', { context: 'property' })}`) + const swiftBridgeInitializerParameters = properties + .map(({ property, parameterType }) => { + const type = parameterType.getCode('swift', { context: 'property' }) + return `_ ${property.escapedName}: ${type}` + }) .join(', ') - const swiftBridgeInitializerArguments = swiftType.properties.map((p) => `${p.escapedName}: ${p.toSwift(p.escapedName)}`).join(', ') - const swiftBridgeGetters = swiftType.properties.map((p) => { - const swiftGetterBody = `return ${p.fromSwift(`self.${p.escapedName}`)}` + const swiftBridgeInitializers: string[] = [] + const swiftBridgeInitializerArguments = properties.map(({ property, parameterType }) => { + const value = parameterType.toSwift(property.escapedName) + if (parameterType instanceof SwiftCxxBorrowedPointerType) { + const name = `__${property.escapedName}` + swiftBridgeInitializers.push(`let ${name} = ${value}`) + return `${property.escapedName}: ${name}` + } + return `${property.escapedName}: ${value}` + }) + const swiftBridgeReturnCode = ` +return Self(${swiftBridgeInitializerArguments.join(', ')}) +`.trim() + const swiftBridgeInitializerBody = [...swiftBridgeInitializers, swiftBridgeReturnCode].join('\n') + const swiftBridgeGetters = properties.map(({ property, returnType }) => { + const getterName = capitalizeName(property.escapedName) + const swiftValue = `self.${property.escapedName}` + const swiftBridgeValue = returnType.fromSwift(swiftValue) + let swiftGetterParameters: string + let swiftGetterReturnType: string + let swiftGetterBody: string + if (returnType instanceof SwiftCxxOutPointerType) { + const pointerType = returnType.getCode('swift', { context: 'parameter' }) + swiftGetterParameters = `_ ${SWIFT_RESULT_STORAGE}: ${pointerType}` + swiftGetterReturnType = 'Void' + const swiftInitializeCode = returnType.initialize('swift', SWIFT_RESULT_STORAGE, 'value') + swiftGetterBody = ` +let value = ${swiftBridgeValue} +${swiftInitializeCode} +`.trim() + } else { + swiftGetterParameters = '' + swiftGetterReturnType = returnType.getCode('swift', { context: 'return' }) + swiftGetterBody = `return ${swiftBridgeValue}` + } return ` @_spi(NitroCxx) -public borrowing func get${capitalizeName(p.escapedName)}_cxx() -> ${p.getCode('swift', { context: 'return' })} { - ${swiftGetterBody} +public borrowing func get${getterName}_cxx(${swiftGetterParameters}) -> ${swiftGetterReturnType} { + ${indent(swiftGetterBody, ' ')} } `.trim() }) @@ -59,7 +99,7 @@ public struct ${structType.structName}${conformancePrefix}${protocolNames.join(' @_spi(NitroCxx) public static func init_cxx(${swiftBridgeInitializerParameters}) -> Self { - return Self(${swiftBridgeInitializerArguments}) + ${indent(swiftBridgeInitializerBody, ' ')} } ${indent(swiftBridgeGetters.join('\n\n'), ' ')} @@ -68,13 +108,31 @@ public struct ${structType.structName}${conformancePrefix}${protocolNames.join(' } const cppStructType = swiftType.getCode('c++', { context: 'value' }) - const cppInitializerArguments = swiftType.properties.map( - (p) => - `JSIConverter<${p.getCode('c++', { context: 'type-argument' })}>::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "${p.name}")))` - ) - const cppPropertyConversions = swiftType.properties.map((p) => { - const swiftGetterName = `get${capitalizeName(p.escapedName)}_cxx` - return `object.setProperty(runtime, PropNameIDCache::get(runtime, "${p.name}"), JSIConverter<${p.getCode('c++', { context: 'type-argument' })}>::toJSI(runtime, value.${swiftGetterName}()));` + const cppInitializerCode = createCppStructInitializer({ + type: cppStructType, + properties: properties, + getCppValue: (property) => { + const type = property.getCode('c++', { context: 'type-argument' }) + return `JSIConverter<${type}>::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "${property.name}")))` + }, + }) + const cppPropertyConversions = properties.map((propertyBridge) => { + const { property, returnType } = propertyBridge + const cppType = property.getCode('c++', { context: 'type-argument' }) + const cppGetter = createCppStructGetter({ property: propertyBridge, receiver: 'value' }) + let cppGetterCode: string + let cppValue: string + if (returnType instanceof SwiftCxxOutPointerType) { + cppValue = `__${property.escapedName}` + cppGetterCode = `auto ${cppValue} = ${cppGetter};` + } else { + cppValue = cppGetter + cppGetterCode = '' + } + const cppSetPropertyCode = ` +object.setProperty(runtime, PropNameIDCache::get(runtime, "${property.name}"), JSIConverter<${cppType}>::toJSI(runtime, ${cppValue})); +`.trim() + return [cppGetterCode, cppSetPropertyCode].filter((code) => code.length > 0).join('\n') }) const cppPropertyChecks = swiftType.properties.map( (p) => @@ -82,7 +140,7 @@ public struct ${structType.structName}${conformancePrefix}${protocolNames.join(' ) const jsToSwiftConverterCode = ` auto object = value.asObject(runtime); -return ${cppStructType}::init_cxx(${cppInitializerArguments.join(', ')}); +${cppInitializerCode} `.trim() const swiftToJSConverterCode = ` jsi::Object object(runtime); @@ -109,6 +167,7 @@ return ${cppPropertyChecks.join(' && ')}; space: 'system', }, }, + ...getStructBridgeCppImports(swiftType), ], fromJSI: jsToSwiftConverterCode, toJSI: swiftToJSConverterCode, @@ -124,27 +183,40 @@ interface SwiftStructCppConverterOptions { export function createSwiftStructCppConverter({ type, cppType }: SwiftStructCppConverterOptions): SwiftCppConverter { const structType = getTypeAs(cppType, StructType) + const propertyBridges = getSwiftStructPropertyBridges(type) const properties = structType.properties.map((property) => { - const swiftProperty = type.properties.find((candidate) => candidate.name === property.name) - if (swiftProperty == null) throw new Error(`Missing Swift property ${type.name}.${property.name}`) - return { native: property, swift: swiftProperty } + const bridge = propertyBridges.find((candidate) => candidate.property.name === property.name) + if (bridge == null) throw new Error(`Missing Swift property ${type.name}.${property.name}`) + return { native: property, swift: bridge.property, bridge: bridge } }) const nativeStructType = structType.getCode('c++', { context: 'value', fullyQualified: true }) - const cppInitializerValues = properties.map(({ native, swift }) => { + const cppGetterInitializers: string[] = [] + const cppInitializerValues = properties.map(({ native, swift, bridge }) => { const converter = getSwiftConverter({ cppType: native, swiftType: swift }) - const getterName = `get${capitalizeName(swift.escapedName)}_cxx` - return `${converter}::fromSwift(value.${getterName}())` + const cppGetter = createCppStructGetter({ property: bridge, receiver: 'value' }) + let cppValue: string + if (bridge.returnType instanceof SwiftCxxOutPointerType) { + cppValue = `__${swift.escapedName}` + cppGetterInitializers.push(`auto ${cppValue} = ${cppGetter};`) + } else { + cppValue = cppGetter + } + return `${converter}::fromSwift(${cppValue})` }) - const swiftInitializerValues = properties.map(({ native, swift }) => { - const converter = getSwiftConverter({ cppType: native, swiftType: swift }) - return `${converter}::toSwift(value.${native.escapedName})` + const cppToSwiftConverterCode = createCppStructInitializer({ + type: 'SwiftType', + properties: propertyBridges, + getCppValue: (property) => { + const native = structType.properties.find((candidate) => candidate.name === property.name) + if (native == null) throw new Error(`Missing C++ property ${type.name}.${property.name}`) + const converter = getSwiftConverter({ cppType: native, swiftType: property }) + return `${converter}::toSwift(value.${native.escapedName})` + }, }) - const swiftToCppConverterCode = ` + const cppReturnCode = ` return ${nativeStructType}(${cppInitializerValues.join(', ')}); `.trim() - const cppToSwiftConverterCode = ` -return SwiftType::init_cxx(${swiftInitializerValues.join(', ')}); -`.trim() + const swiftToCppConverterCode = [...cppGetterInitializers, cppReturnCode].join('\n') const dependencies = properties.map(({ native, swift }) => swift.getCppConverter(native)) const name = `${type.name}+${type.moduleName}` return createSwiftConverter({ @@ -155,5 +227,17 @@ return SwiftType::init_cxx(${swiftInitializerValues.join(', ')}); fromSwift: swiftToCppConverterCode, toSwift: cppToSwiftConverterCode, dependencies: dependencies, + imports: getStructBridgeCppImports(type), }) } + +function getStructBridgeCppImports(type: SwiftStructType): SourceImport<'c++'>[] { + const hasOpaqueProperties = type.properties.some((property) => property.requiresOpaqueCxxBridge) + if (hasOpaqueProperties) { + return [ + { language: 'c++', declaration: { header: 'NitroModules/SwiftResult.hpp', space: 'system' } }, + { language: 'c++', declaration: { header: 'memory', space: 'system' } }, + ] + } + return [] +} diff --git a/packages/nitrogen/src/syntax/swift/SwiftStructHelpers.ts b/packages/nitrogen/src/syntax/swift/SwiftStructHelpers.ts new file mode 100644 index 0000000000..a029bdf9e8 --- /dev/null +++ b/packages/nitrogen/src/syntax/swift/SwiftStructHelpers.ts @@ -0,0 +1,64 @@ +import { capitalizeName } from '../../utils.js' +import { getSwiftBridgeParameterType, getSwiftBridgeReturnType, SWIFT_RESULT_STORAGE } from './SwiftMethodHelpers.js' +import { SwiftCxxBorrowedPointerType } from './types/SwiftCxxBorrowedPointerType.js' +import { SwiftCxxOutPointerType } from './types/SwiftCxxOutPointerType.js' +import type { SwiftNamedType } from './types/SwiftNamedType.js' +import type { SwiftStructType } from './types/SwiftStructType.js' +import type { SwiftType } from './types/SwiftType.js' + +export interface SwiftStructPropertyBridge { + property: SwiftNamedType + parameterType: SwiftType + returnType: SwiftType +} + +export function getSwiftStructPropertyBridges(type: SwiftStructType): SwiftStructPropertyBridge[] { + return type.properties.map((property) => ({ + property: property, + parameterType: getSwiftBridgeParameterType(property), + returnType: getSwiftBridgeReturnType({ type: property, isThrowing: false }), + })) +} + +interface CppStructInitializerOptions { + type: string + properties: SwiftStructPropertyBridge[] + getCppValue: (property: SwiftNamedType) => string +} + +/** Keeps borrowed C++ values alive until Swift has copied them into the struct. */ +export function createCppStructInitializer({ type, properties, getCppValue }: CppStructInitializerOptions): string { + const cppInitializers: string[] = [] + const cppArguments = properties.map(({ property, parameterType }) => { + const value = getCppValue(property) + if (parameterType instanceof SwiftCxxBorrowedPointerType) { + const name = `__${property.escapedName}` + cppInitializers.push(`auto ${name} = ${value};`) + return `std::addressof(${name})` + } + return value + }) + const cppInitializerCode = ` +return ${type}::init_cxx(${cppArguments.join(', ')}); +`.trim() + return [...cppInitializers, cppInitializerCode].join('\n') +} + +interface CppStructGetterOptions { + property: SwiftStructPropertyBridge + receiver: string +} + +export function createCppStructGetter({ property: { property, returnType }, receiver }: CppStructGetterOptions): string { + const getterName = capitalizeName(property.escapedName) + if (returnType instanceof SwiftCxxOutPointerType) { + const cppInitializeCode = ` +${receiver}.get${getterName}_cxx(${SWIFT_RESULT_STORAGE}); +`.trim() + return returnType.allocate('c++', { + storage: SWIFT_RESULT_STORAGE, + body: () => cppInitializeCode, + }) + } + return `${receiver}.get${getterName}_cxx()` +} diff --git a/tests/standalone/nitro-test/nitrogen/generated/apple/c++/JsStyleStruct-Swift.cpp b/tests/standalone/nitro-test/nitrogen/generated/apple/c++/JsStyleStruct-Swift.cpp index 8cd1bbee06..9ef7ca1d97 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/apple/c++/JsStyleStruct-Swift.cpp +++ b/tests/standalone/nitro-test/nitrogen/generated/apple/c++/JsStyleStruct-Swift.cpp @@ -8,18 +8,24 @@ #include #include #include +#include +#include namespace margelo::nitro { ::NitroTest::JsStyleStruct JSIConverter<::NitroTest::JsStyleStruct>::fromJSI(jsi::Runtime& runtime, const jsi::Value& value) { auto object = value.asObject(runtime); - return ::NitroTest::JsStyleStruct::init_cxx(JSIConverter::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "value"))), JSIConverter>::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "onChanged")))); + auto __onChanged = JSIConverter>::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "onChanged"))); + return ::NitroTest::JsStyleStruct::init_cxx(JSIConverter::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "value"))), std::addressof(__onChanged)); } jsi::Value JSIConverter<::NitroTest::JsStyleStruct>::toJSI(jsi::Runtime& runtime, const ::NitroTest::JsStyleStruct& value) { jsi::Object object(runtime); object.setProperty(runtime, PropNameIDCache::get(runtime, "value"), JSIConverter::toJSI(runtime, value.getValue_cxx())); - object.setProperty(runtime, PropNameIDCache::get(runtime, "onChanged"), JSIConverter>::toJSI(runtime, value.getOnChanged_cxx())); + auto __onChanged = makeCxxValue>([&](void* __outResult) { + value.getOnChanged_cxx(__outResult); + }); + object.setProperty(runtime, PropNameIDCache::get(runtime, "onChanged"), JSIConverter>::toJSI(runtime, __onChanged)); return object; } diff --git a/tests/standalone/nitro-test/nitrogen/generated/apple/swift/JsStyleStruct.swift b/tests/standalone/nitro-test/nitrogen/generated/apple/swift/JsStyleStruct.swift index 2467139a33..4d79eede73 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/apple/swift/JsStyleStruct.swift +++ b/tests/standalone/nitro-test/nitrogen/generated/apple/swift/JsStyleStruct.swift @@ -20,8 +20,9 @@ public struct JsStyleStruct: Sendable { } @_spi(NitroCxx) - public static func init_cxx(_ value: Double, _ onChanged: margelo.nitro.test.swift_bridge.Callback_void_double) -> Self { - return Self(value: value, onChanged: onChanged) + public static func init_cxx(_ value: Double, _ onChanged: UnsafeRawPointer) -> Self { + let __onChanged = onChanged.load(as: margelo.nitro.test.swift_bridge.Callback_void_double.self) + return Self(value: value, onChanged: __onChanged) } @_spi(NitroCxx) @@ -30,7 +31,8 @@ public struct JsStyleStruct: Sendable { } @_spi(NitroCxx) - public borrowing func getOnChanged_cxx() -> margelo.nitro.test.swift_bridge.Callback_void_double { - return self.onChanged.toCpp() + public borrowing func getOnChanged_cxx(_ __outResult: UnsafeMutableRawPointer) -> Void { + let value = self.onChanged.toCpp() + __outResult.initializeMemory(as: margelo.nitro.test.swift_bridge.Callback_void_double.self, repeating: value, count: 1) } } From c93b29cc24fc1d0dbad1ebba584eef4e82d90a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 27 Oct 2026 15:00:27 +0100 Subject: [PATCH 2/5] refactor(swift): remove style-only struct bridge temporaries --- .../nitrogen/src/syntax/swift/SwiftStruct.ts | 79 ++++--------------- .../apple/c++/JsStyleStruct-Swift.cpp | 5 +- .../generated/apple/swift/JsStyleStruct.swift | 6 +- 3 files changed, 20 insertions(+), 70 deletions(-) diff --git a/packages/nitrogen/src/syntax/swift/SwiftStruct.ts b/packages/nitrogen/src/syntax/swift/SwiftStruct.ts index 5e91c87ce7..6d57c695a6 100644 --- a/packages/nitrogen/src/syntax/swift/SwiftStruct.ts +++ b/packages/nitrogen/src/syntax/swift/SwiftStruct.ts @@ -12,7 +12,6 @@ import { getProtocolConformanceImports, getSwiftImports } from './getSwiftImport import { SWIFT_RESULT_STORAGE } from './SwiftMethodHelpers.js' import { createCppStructGetter, createCppStructInitializer, getSwiftStructPropertyBridges } from './SwiftStructHelpers.js' import { SwiftCxxOutPointerType } from './types/SwiftCxxOutPointerType.js' -import { SwiftCxxBorrowedPointerType } from './types/SwiftCxxBorrowedPointerType.js' export function createSwiftStructBridge(swiftType: SwiftStructType): SourceFile[] { const structType = swiftType.type @@ -26,50 +25,28 @@ export function createSwiftStructBridge(swiftType: SwiftStructType): SourceFile[ .join(', ') const swiftPropertyInitializers = structType.properties.map((p) => `self.${p.escapedName} = ${p.escapedName}`) const swiftBridgeInitializerParameters = properties - .map(({ property, parameterType }) => { - const type = parameterType.getCode('swift', { context: 'property' }) - return `_ ${property.escapedName}: ${type}` - }) + .map(({ property, parameterType }) => `_ ${property.escapedName}: ${parameterType.getCode('swift', { context: 'property' })}`) + .join(', ') + const swiftBridgeInitializerArguments = properties + .map(({ property, parameterType }) => `${property.escapedName}: ${parameterType.toSwift(property.escapedName)}`) .join(', ') - const swiftBridgeInitializers: string[] = [] - const swiftBridgeInitializerArguments = properties.map(({ property, parameterType }) => { - const value = parameterType.toSwift(property.escapedName) - if (parameterType instanceof SwiftCxxBorrowedPointerType) { - const name = `__${property.escapedName}` - swiftBridgeInitializers.push(`let ${name} = ${value}`) - return `${property.escapedName}: ${name}` - } - return `${property.escapedName}: ${value}` - }) - const swiftBridgeReturnCode = ` -return Self(${swiftBridgeInitializerArguments.join(', ')}) -`.trim() - const swiftBridgeInitializerBody = [...swiftBridgeInitializers, swiftBridgeReturnCode].join('\n') const swiftBridgeGetters = properties.map(({ property, returnType }) => { - const getterName = capitalizeName(property.escapedName) - const swiftValue = `self.${property.escapedName}` - const swiftBridgeValue = returnType.fromSwift(swiftValue) let swiftGetterParameters: string let swiftGetterReturnType: string let swiftGetterBody: string if (returnType instanceof SwiftCxxOutPointerType) { - const pointerType = returnType.getCode('swift', { context: 'parameter' }) - swiftGetterParameters = `_ ${SWIFT_RESULT_STORAGE}: ${pointerType}` + swiftGetterParameters = `_ ${SWIFT_RESULT_STORAGE}: ${returnType.getCode('swift', { context: 'parameter' })}` swiftGetterReturnType = 'Void' - const swiftInitializeCode = returnType.initialize('swift', SWIFT_RESULT_STORAGE, 'value') - swiftGetterBody = ` -let value = ${swiftBridgeValue} -${swiftInitializeCode} -`.trim() + swiftGetterBody = returnType.initialize('swift', SWIFT_RESULT_STORAGE, returnType.fromSwift(`self.${property.escapedName}`)) } else { swiftGetterParameters = '' swiftGetterReturnType = returnType.getCode('swift', { context: 'return' }) - swiftGetterBody = `return ${swiftBridgeValue}` + swiftGetterBody = `return ${returnType.fromSwift(`self.${property.escapedName}`)}` } return ` @_spi(NitroCxx) -public borrowing func get${getterName}_cxx(${swiftGetterParameters}) -> ${swiftGetterReturnType} { - ${indent(swiftGetterBody, ' ')} +public borrowing func get${capitalizeName(property.escapedName)}_cxx(${swiftGetterParameters}) -> ${swiftGetterReturnType} { + ${swiftGetterBody} } `.trim() }) @@ -99,7 +76,7 @@ public struct ${structType.structName}${conformancePrefix}${protocolNames.join(' @_spi(NitroCxx) public static func init_cxx(${swiftBridgeInitializerParameters}) -> Self { - ${indent(swiftBridgeInitializerBody, ' ')} + return Self(${swiftBridgeInitializerArguments}) } ${indent(swiftBridgeGetters.join('\n\n'), ' ')} @@ -111,28 +88,13 @@ public struct ${structType.structName}${conformancePrefix}${protocolNames.join(' const cppInitializerCode = createCppStructInitializer({ type: cppStructType, properties: properties, - getCppValue: (property) => { - const type = property.getCode('c++', { context: 'type-argument' }) - return `JSIConverter<${type}>::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "${property.name}")))` - }, + getCppValue: (property) => + `JSIConverter<${property.getCode('c++', { context: 'type-argument' })}>::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "${property.name}")))`, }) const cppPropertyConversions = properties.map((propertyBridge) => { - const { property, returnType } = propertyBridge - const cppType = property.getCode('c++', { context: 'type-argument' }) + const { property } = propertyBridge const cppGetter = createCppStructGetter({ property: propertyBridge, receiver: 'value' }) - let cppGetterCode: string - let cppValue: string - if (returnType instanceof SwiftCxxOutPointerType) { - cppValue = `__${property.escapedName}` - cppGetterCode = `auto ${cppValue} = ${cppGetter};` - } else { - cppValue = cppGetter - cppGetterCode = '' - } - const cppSetPropertyCode = ` -object.setProperty(runtime, PropNameIDCache::get(runtime, "${property.name}"), JSIConverter<${cppType}>::toJSI(runtime, ${cppValue})); -`.trim() - return [cppGetterCode, cppSetPropertyCode].filter((code) => code.length > 0).join('\n') + return `object.setProperty(runtime, PropNameIDCache::get(runtime, "${property.name}"), JSIConverter<${property.getCode('c++', { context: 'type-argument' })}>::toJSI(runtime, ${cppGetter}));` }) const cppPropertyChecks = swiftType.properties.map( (p) => @@ -190,18 +152,10 @@ export function createSwiftStructCppConverter({ type, cppType }: SwiftStructCppC return { native: property, swift: bridge.property, bridge: bridge } }) const nativeStructType = structType.getCode('c++', { context: 'value', fullyQualified: true }) - const cppGetterInitializers: string[] = [] const cppInitializerValues = properties.map(({ native, swift, bridge }) => { const converter = getSwiftConverter({ cppType: native, swiftType: swift }) const cppGetter = createCppStructGetter({ property: bridge, receiver: 'value' }) - let cppValue: string - if (bridge.returnType instanceof SwiftCxxOutPointerType) { - cppValue = `__${swift.escapedName}` - cppGetterInitializers.push(`auto ${cppValue} = ${cppGetter};`) - } else { - cppValue = cppGetter - } - return `${converter}::fromSwift(${cppValue})` + return `${converter}::fromSwift(${cppGetter})` }) const cppToSwiftConverterCode = createCppStructInitializer({ type: 'SwiftType', @@ -213,10 +167,9 @@ export function createSwiftStructCppConverter({ type, cppType }: SwiftStructCppC return `${converter}::toSwift(value.${native.escapedName})` }, }) - const cppReturnCode = ` + const swiftToCppConverterCode = ` return ${nativeStructType}(${cppInitializerValues.join(', ')}); `.trim() - const swiftToCppConverterCode = [...cppGetterInitializers, cppReturnCode].join('\n') const dependencies = properties.map(({ native, swift }) => swift.getCppConverter(native)) const name = `${type.name}+${type.moduleName}` return createSwiftConverter({ diff --git a/tests/standalone/nitro-test/nitrogen/generated/apple/c++/JsStyleStruct-Swift.cpp b/tests/standalone/nitro-test/nitrogen/generated/apple/c++/JsStyleStruct-Swift.cpp index 9ef7ca1d97..dfd8e20876 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/apple/c++/JsStyleStruct-Swift.cpp +++ b/tests/standalone/nitro-test/nitrogen/generated/apple/c++/JsStyleStruct-Swift.cpp @@ -22,10 +22,9 @@ namespace margelo::nitro { jsi::Value JSIConverter<::NitroTest::JsStyleStruct>::toJSI(jsi::Runtime& runtime, const ::NitroTest::JsStyleStruct& value) { jsi::Object object(runtime); object.setProperty(runtime, PropNameIDCache::get(runtime, "value"), JSIConverter::toJSI(runtime, value.getValue_cxx())); - auto __onChanged = makeCxxValue>([&](void* __outResult) { + object.setProperty(runtime, PropNameIDCache::get(runtime, "onChanged"), JSIConverter>::toJSI(runtime, makeCxxValue>([&](void* __outResult) { value.getOnChanged_cxx(__outResult); - }); - object.setProperty(runtime, PropNameIDCache::get(runtime, "onChanged"), JSIConverter>::toJSI(runtime, __onChanged)); + }))); return object; } diff --git a/tests/standalone/nitro-test/nitrogen/generated/apple/swift/JsStyleStruct.swift b/tests/standalone/nitro-test/nitrogen/generated/apple/swift/JsStyleStruct.swift index 4d79eede73..dbcd455096 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/apple/swift/JsStyleStruct.swift +++ b/tests/standalone/nitro-test/nitrogen/generated/apple/swift/JsStyleStruct.swift @@ -21,8 +21,7 @@ public struct JsStyleStruct: Sendable { @_spi(NitroCxx) public static func init_cxx(_ value: Double, _ onChanged: UnsafeRawPointer) -> Self { - let __onChanged = onChanged.load(as: margelo.nitro.test.swift_bridge.Callback_void_double.self) - return Self(value: value, onChanged: __onChanged) + return Self(value: value, onChanged: onChanged.load(as: margelo.nitro.test.swift_bridge.Callback_void_double.self)) } @_spi(NitroCxx) @@ -32,7 +31,6 @@ public struct JsStyleStruct: Sendable { @_spi(NitroCxx) public borrowing func getOnChanged_cxx(_ __outResult: UnsafeMutableRawPointer) -> Void { - let value = self.onChanged.toCpp() - __outResult.initializeMemory(as: margelo.nitro.test.swift_bridge.Callback_void_double.self, repeating: value, count: 1) + __outResult.initializeMemory(as: margelo.nitro.test.swift_bridge.Callback_void_double.self, repeating: self.onChanged.toCpp(), count: 1) } } From dc14a6d4d96f5251dfed100d63c189358f34cd00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 27 Oct 2026 15:35:08 +0100 Subject: [PATCH 3/5] refactor(swift): keep struct bridge fix focused --- .../src/syntax/swift/SwiftMethodHelpers.ts | 49 +++----- .../nitrogen/src/syntax/swift/SwiftStruct.ts | 117 ++++++++++++------ .../src/syntax/swift/SwiftStructHelpers.ts | 64 ---------- 3 files changed, 98 insertions(+), 132 deletions(-) delete mode 100644 packages/nitrogen/src/syntax/swift/SwiftStructHelpers.ts diff --git a/packages/nitrogen/src/syntax/swift/SwiftMethodHelpers.ts b/packages/nitrogen/src/syntax/swift/SwiftMethodHelpers.ts index 851a965f7d..b9c533c173 100644 --- a/packages/nitrogen/src/syntax/swift/SwiftMethodHelpers.ts +++ b/packages/nitrogen/src/syntax/swift/SwiftMethodHelpers.ts @@ -63,39 +63,16 @@ export interface SwiftMethodInfo { resultType: SwiftResultType } -export function getSwiftBridgeParameterType(type: SwiftType): SwiftType { - if (type.requiresOpaqueCxxBridge) { - return new SwiftCxxBorrowedPointerType(type) - } - return type -} - -interface SwiftBridgeReturnTypeOptions { - type: SwiftType - isThrowing: boolean -} - -export function getSwiftBridgeReturnType({ type, isThrowing }: SwiftBridgeReturnTypeOptions): SwiftType { - if (type.requiresOpaqueCxxBridge) { - return new SwiftCxxOutPointerType(type) - } - if (isThrowing && type.isReferenceType) { - // A throwing class result crosses the boundary as an owned opaque pointer. - return new SwiftRetainedPointerType(type) - } - if (isThrowing && !type.canImportFromCpp) { - // Swift cannot import Result while a Swift-native T is incomplete. - // Return Result alongside its value buffer; Core constructs Result. - return new SwiftOutPointerType(type) - } - return type -} - export function prepareSwiftMethod(nativeMethod: Method, isThrowing: boolean, config: NitroConfig = NitroConfig.current): SwiftMethodInfo { const returnType = wrapSwiftType(nativeMethod.returnType, config) const parameters = nativeMethod.parameters.map((parameter) => { const swiftType = wrapSwiftType(parameter.type, config) - const exportedType = getSwiftBridgeParameterType(swiftType) + let exportedType: SwiftType + if (swiftType.requiresOpaqueCxxBridge) { + exportedType = new SwiftCxxBorrowedPointerType(swiftType) + } else { + exportedType = swiftType + } return { native: parameter, swift: new Parameter(parameter.name, swiftType), @@ -103,7 +80,19 @@ export function prepareSwiftMethod(nativeMethod: Method, isThrowing: boolean, co exported: { name: parameter.type.escapedName, type: exportedType }, } }) - const exportedReturnType = getSwiftBridgeReturnType({ type: returnType, isThrowing: isThrowing }) + let exportedReturnType: SwiftType + if (returnType.requiresOpaqueCxxBridge) { + exportedReturnType = new SwiftCxxOutPointerType(returnType) + } else if (isThrowing && returnType.isReferenceType) { + // A throwing class result crosses the boundary as an owned opaque pointer. + exportedReturnType = new SwiftRetainedPointerType(returnType) + } else if (isThrowing && !returnType.canImportFromCpp) { + // Swift cannot import Result while a Swift-native T is incomplete. + // Return Result alongside its value buffer; Core constructs Result. + exportedReturnType = new SwiftOutPointerType(returnType) + } else { + exportedReturnType = returnType + } const resultType = getSwiftResultType(exportedReturnType, config) return { nativeMethod: nativeMethod, diff --git a/packages/nitrogen/src/syntax/swift/SwiftStruct.ts b/packages/nitrogen/src/syntax/swift/SwiftStruct.ts index 6d57c695a6..b50e23532a 100644 --- a/packages/nitrogen/src/syntax/swift/SwiftStruct.ts +++ b/packages/nitrogen/src/syntax/swift/SwiftStruct.ts @@ -10,12 +10,12 @@ import type { SwiftStructType } from './types/SwiftStructType.js' import { getImportsCode } from '../getImportCode.js' import { getProtocolConformanceImports, getSwiftImports } from './getSwiftImports.js' import { SWIFT_RESULT_STORAGE } from './SwiftMethodHelpers.js' -import { createCppStructGetter, createCppStructInitializer, getSwiftStructPropertyBridges } from './SwiftStructHelpers.js' +import { SwiftCxxBorrowedPointerType } from './types/SwiftCxxBorrowedPointerType.js' import { SwiftCxxOutPointerType } from './types/SwiftCxxOutPointerType.js' +import { SwiftNamedType } from './types/SwiftNamedType.js' export function createSwiftStructBridge(swiftType: SwiftStructType): SourceFile[] { const structType = swiftType.type - const properties = getSwiftStructPropertyBridges(swiftType) const conformances = swiftType.getProtocolConformances() const protocolNames = conformances.map((conformance) => conformance.name) const conformancePrefix = protocolNames.length > 0 ? ': ' : '' @@ -24,28 +24,35 @@ export function createSwiftStructBridge(swiftType: SwiftStructType): SourceFile[ .map((p) => `${p.escapedName}: ${p.getCode('swift', { context: 'property' })}`) .join(', ') const swiftPropertyInitializers = structType.properties.map((p) => `self.${p.escapedName} = ${p.escapedName}`) - const swiftBridgeInitializerParameters = properties - .map(({ property, parameterType }) => `_ ${property.escapedName}: ${parameterType.getCode('swift', { context: 'property' })}`) - .join(', ') - const swiftBridgeInitializerArguments = properties - .map(({ property, parameterType }) => `${property.escapedName}: ${parameterType.toSwift(property.escapedName)}`) + const swiftBridgeProperties = swiftType.properties.map((p) => { + if (p.requiresOpaqueCxxBridge) { + const pointerType = new SwiftCxxBorrowedPointerType(p) + return new SwiftNamedType(p.name, pointerType) + } + return p + }) + const swiftBridgeInitializerParameters = swiftBridgeProperties + .map((p) => `_ ${p.escapedName}: ${p.getCode('swift', { context: 'property' })}`) .join(', ') - const swiftBridgeGetters = properties.map(({ property, returnType }) => { + const swiftBridgeInitializerArguments = swiftBridgeProperties.map((p) => `${p.escapedName}: ${p.toSwift(p.escapedName)}`).join(', ') + const swiftBridgeGetters = swiftType.properties.map((p) => { + const swiftGetterValue = p.fromSwift(`self.${p.escapedName}`) let swiftGetterParameters: string let swiftGetterReturnType: string let swiftGetterBody: string - if (returnType instanceof SwiftCxxOutPointerType) { + if (p.requiresOpaqueCxxBridge) { + const returnType = new SwiftCxxOutPointerType(p) swiftGetterParameters = `_ ${SWIFT_RESULT_STORAGE}: ${returnType.getCode('swift', { context: 'parameter' })}` swiftGetterReturnType = 'Void' - swiftGetterBody = returnType.initialize('swift', SWIFT_RESULT_STORAGE, returnType.fromSwift(`self.${property.escapedName}`)) + swiftGetterBody = returnType.initialize('swift', SWIFT_RESULT_STORAGE, swiftGetterValue) } else { swiftGetterParameters = '' - swiftGetterReturnType = returnType.getCode('swift', { context: 'return' }) - swiftGetterBody = `return ${returnType.fromSwift(`self.${property.escapedName}`)}` + swiftGetterReturnType = p.getCode('swift', { context: 'return' }) + swiftGetterBody = `return ${swiftGetterValue}` } return ` @_spi(NitroCxx) -public borrowing func get${capitalizeName(property.escapedName)}_cxx(${swiftGetterParameters}) -> ${swiftGetterReturnType} { +public borrowing func get${capitalizeName(p.escapedName)}_cxx(${swiftGetterParameters}) -> ${swiftGetterReturnType} { ${swiftGetterBody} } `.trim() @@ -85,16 +92,14 @@ public struct ${structType.structName}${conformancePrefix}${protocolNames.join(' } const cppStructType = swiftType.getCode('c++', { context: 'value' }) - const cppInitializerCode = createCppStructInitializer({ - type: cppStructType, - properties: properties, - getCppValue: (property) => - `JSIConverter<${property.getCode('c++', { context: 'type-argument' })}>::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "${property.name}")))`, - }) - const cppPropertyConversions = properties.map((propertyBridge) => { - const { property } = propertyBridge - const cppGetter = createCppStructGetter({ property: propertyBridge, receiver: 'value' }) - return `object.setProperty(runtime, PropNameIDCache::get(runtime, "${property.name}"), JSIConverter<${property.getCode('c++', { context: 'type-argument' })}>::toJSI(runtime, ${cppGetter}));` + const cppInitializerArguments = swiftType.properties.map((p) => ({ + property: p, + value: `JSIConverter<${p.getCode('c++', { context: 'type-argument' })}>::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "${p.name}")))`, + })) + const cppInitializerCode = createCppStructInitializer({ type: cppStructType, properties: cppInitializerArguments }) + const cppPropertyConversions = swiftType.properties.map((p) => { + const cppGetter = createCppStructGetter({ property: p, receiver: 'value' }) + return `object.setProperty(runtime, PropNameIDCache::get(runtime, "${p.name}"), JSIConverter<${p.getCode('c++', { context: 'type-argument' })}>::toJSI(runtime, ${cppGetter}));` }) const cppPropertyChecks = swiftType.properties.map( (p) => @@ -145,31 +150,25 @@ interface SwiftStructCppConverterOptions { export function createSwiftStructCppConverter({ type, cppType }: SwiftStructCppConverterOptions): SwiftCppConverter { const structType = getTypeAs(cppType, StructType) - const propertyBridges = getSwiftStructPropertyBridges(type) const properties = structType.properties.map((property) => { - const bridge = propertyBridges.find((candidate) => candidate.property.name === property.name) - if (bridge == null) throw new Error(`Missing Swift property ${type.name}.${property.name}`) - return { native: property, swift: bridge.property, bridge: bridge } + const swiftProperty = type.properties.find((candidate) => candidate.name === property.name) + if (swiftProperty == null) throw new Error(`Missing Swift property ${type.name}.${property.name}`) + return { native: property, swift: swiftProperty } }) const nativeStructType = structType.getCode('c++', { context: 'value', fullyQualified: true }) - const cppInitializerValues = properties.map(({ native, swift, bridge }) => { + const cppInitializerValues = properties.map(({ native, swift }) => { const converter = getSwiftConverter({ cppType: native, swiftType: swift }) - const cppGetter = createCppStructGetter({ property: bridge, receiver: 'value' }) + const cppGetter = createCppStructGetter({ property: swift, receiver: 'value' }) return `${converter}::fromSwift(${cppGetter})` }) - const cppToSwiftConverterCode = createCppStructInitializer({ - type: 'SwiftType', - properties: propertyBridges, - getCppValue: (property) => { - const native = structType.properties.find((candidate) => candidate.name === property.name) - if (native == null) throw new Error(`Missing C++ property ${type.name}.${property.name}`) - const converter = getSwiftConverter({ cppType: native, swiftType: property }) - return `${converter}::toSwift(value.${native.escapedName})` - }, + const swiftInitializerValues = properties.map(({ native, swift }) => { + const converter = getSwiftConverter({ cppType: native, swiftType: swift }) + return { property: swift, value: `${converter}::toSwift(value.${native.escapedName})` } }) const swiftToCppConverterCode = ` return ${nativeStructType}(${cppInitializerValues.join(', ')}); `.trim() + const cppToSwiftConverterCode = createCppStructInitializer({ type: 'SwiftType', properties: swiftInitializerValues }) const dependencies = properties.map(({ native, swift }) => swift.getCppConverter(native)) const name = `${type.name}+${type.moduleName}` return createSwiftConverter({ @@ -184,6 +183,48 @@ return ${nativeStructType}(${cppInitializerValues.join(', ')}); }) } +interface CppStructInitializerOptions { + type: string + properties: { property: SwiftNamedType; value: string }[] +} + +/** Keeps borrowed C++ values alive until Swift has copied them into the struct. */ +function createCppStructInitializer({ type, properties }: CppStructInitializerOptions): string { + const cppInitializers: string[] = [] + const cppArguments = properties.map(({ property, value }) => { + if (property.requiresOpaqueCxxBridge) { + const name = `__${property.escapedName}` + cppInitializers.push(`auto ${name} = ${value};`) + return `std::addressof(${name})` + } + return value + }) + const cppInitializerCode = ` +return ${type}::init_cxx(${cppArguments.join(', ')}); +`.trim() + return [...cppInitializers, cppInitializerCode].join('\n') +} + +interface CppStructGetterOptions { + property: SwiftNamedType + receiver: string +} + +function createCppStructGetter({ property, receiver }: CppStructGetterOptions): string { + const getterName = capitalizeName(property.escapedName) + if (property.requiresOpaqueCxxBridge) { + const returnType = new SwiftCxxOutPointerType(property) + const cppInitializeCode = ` +${receiver}.get${getterName}_cxx(${SWIFT_RESULT_STORAGE}); +`.trim() + return returnType.allocate('c++', { + storage: SWIFT_RESULT_STORAGE, + body: () => cppInitializeCode, + }) + } + return `${receiver}.get${getterName}_cxx()` +} + function getStructBridgeCppImports(type: SwiftStructType): SourceImport<'c++'>[] { const hasOpaqueProperties = type.properties.some((property) => property.requiresOpaqueCxxBridge) if (hasOpaqueProperties) { diff --git a/packages/nitrogen/src/syntax/swift/SwiftStructHelpers.ts b/packages/nitrogen/src/syntax/swift/SwiftStructHelpers.ts deleted file mode 100644 index a029bdf9e8..0000000000 --- a/packages/nitrogen/src/syntax/swift/SwiftStructHelpers.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { capitalizeName } from '../../utils.js' -import { getSwiftBridgeParameterType, getSwiftBridgeReturnType, SWIFT_RESULT_STORAGE } from './SwiftMethodHelpers.js' -import { SwiftCxxBorrowedPointerType } from './types/SwiftCxxBorrowedPointerType.js' -import { SwiftCxxOutPointerType } from './types/SwiftCxxOutPointerType.js' -import type { SwiftNamedType } from './types/SwiftNamedType.js' -import type { SwiftStructType } from './types/SwiftStructType.js' -import type { SwiftType } from './types/SwiftType.js' - -export interface SwiftStructPropertyBridge { - property: SwiftNamedType - parameterType: SwiftType - returnType: SwiftType -} - -export function getSwiftStructPropertyBridges(type: SwiftStructType): SwiftStructPropertyBridge[] { - return type.properties.map((property) => ({ - property: property, - parameterType: getSwiftBridgeParameterType(property), - returnType: getSwiftBridgeReturnType({ type: property, isThrowing: false }), - })) -} - -interface CppStructInitializerOptions { - type: string - properties: SwiftStructPropertyBridge[] - getCppValue: (property: SwiftNamedType) => string -} - -/** Keeps borrowed C++ values alive until Swift has copied them into the struct. */ -export function createCppStructInitializer({ type, properties, getCppValue }: CppStructInitializerOptions): string { - const cppInitializers: string[] = [] - const cppArguments = properties.map(({ property, parameterType }) => { - const value = getCppValue(property) - if (parameterType instanceof SwiftCxxBorrowedPointerType) { - const name = `__${property.escapedName}` - cppInitializers.push(`auto ${name} = ${value};`) - return `std::addressof(${name})` - } - return value - }) - const cppInitializerCode = ` -return ${type}::init_cxx(${cppArguments.join(', ')}); -`.trim() - return [...cppInitializers, cppInitializerCode].join('\n') -} - -interface CppStructGetterOptions { - property: SwiftStructPropertyBridge - receiver: string -} - -export function createCppStructGetter({ property: { property, returnType }, receiver }: CppStructGetterOptions): string { - const getterName = capitalizeName(property.escapedName) - if (returnType instanceof SwiftCxxOutPointerType) { - const cppInitializeCode = ` -${receiver}.get${getterName}_cxx(${SWIFT_RESULT_STORAGE}); -`.trim() - return returnType.allocate('c++', { - storage: SWIFT_RESULT_STORAGE, - body: () => cppInitializeCode, - }) - } - return `${receiver}.get${getterName}_cxx()` -} From 876803e1ad8156145ff4128ff8c71aec09dfb576 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 27 Oct 2026 17:23:15 +0100 Subject: [PATCH 4/5] chore(nitrogen): document and format opaque struct bridges --- .../nitrogen/src/syntax/swift/SwiftStruct.ts | 59 +++++++++++++------ 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/packages/nitrogen/src/syntax/swift/SwiftStruct.ts b/packages/nitrogen/src/syntax/swift/SwiftStruct.ts index b50e23532a..34c3742b7d 100644 --- a/packages/nitrogen/src/syntax/swift/SwiftStruct.ts +++ b/packages/nitrogen/src/syntax/swift/SwiftStruct.ts @@ -24,6 +24,9 @@ export function createSwiftStructBridge(swiftType: SwiftStructType): SourceFile[ .map((p) => `${p.escapedName}: ${p.getCode('swift', { context: 'property' })}`) .join(', ') const swiftPropertyInitializers = structType.properties.map((p) => `self.${p.escapedName} = ${p.escapedName}`) + // TODO: Restore direct struct bridge signatures and remove the opaque C++ adapters once our minimum + // Xcode includes https://github.com/swiftlang/swift/pull/93321, + // which fixes https://github.com/swiftlang/swift/issues/93318. const swiftBridgeProperties = swiftType.properties.map((p) => { if (p.requiresOpaqueCxxBridge) { const pointerType = new SwiftCxxBorrowedPointerType(p) @@ -42,18 +45,22 @@ export function createSwiftStructBridge(swiftType: SwiftStructType): SourceFile[ let swiftGetterBody: string if (p.requiresOpaqueCxxBridge) { const returnType = new SwiftCxxOutPointerType(p) - swiftGetterParameters = `_ ${SWIFT_RESULT_STORAGE}: ${returnType.getCode('swift', { context: 'parameter' })}` + swiftGetterParameters = ` +_ ${SWIFT_RESULT_STORAGE}: ${returnType.getCode('swift', { context: 'parameter' })} +`.trim() swiftGetterReturnType = 'Void' swiftGetterBody = returnType.initialize('swift', SWIFT_RESULT_STORAGE, swiftGetterValue) } else { swiftGetterParameters = '' swiftGetterReturnType = p.getCode('swift', { context: 'return' }) - swiftGetterBody = `return ${swiftGetterValue}` + swiftGetterBody = ` +return ${indent(swiftGetterValue, '')} +`.trim() } return ` @_spi(NitroCxx) public borrowing func get${capitalizeName(p.escapedName)}_cxx(${swiftGetterParameters}) -> ${swiftGetterReturnType} { - ${swiftGetterBody} + ${indent(swiftGetterBody, ' ')} } `.trim() }) @@ -92,14 +99,20 @@ public struct ${structType.structName}${conformancePrefix}${protocolNames.join(' } const cppStructType = swiftType.getCode('c++', { context: 'value' }) - const cppInitializerArguments = swiftType.properties.map((p) => ({ - property: p, - value: `JSIConverter<${p.getCode('c++', { context: 'type-argument' })}>::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "${p.name}")))`, - })) - const cppInitializerCode = createCppStructInitializer({ type: cppStructType, properties: cppInitializerArguments }) + const cppInitializerArguments = swiftType.properties.map((p) => { + const cppPropertyType = p.getCode('c++', { context: 'type-argument' }) + const jsToSwiftPropertyCode = ` +JSIConverter<${cppPropertyType}>::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "${p.name}"))) +`.trim() + return { property: p, value: jsToSwiftPropertyCode } + }) + const jsToSwiftInitializerCode = createCppStructInitializer({ type: cppStructType, properties: cppInitializerArguments }) const cppPropertyConversions = swiftType.properties.map((p) => { const cppGetter = createCppStructGetter({ property: p, receiver: 'value' }) - return `object.setProperty(runtime, PropNameIDCache::get(runtime, "${p.name}"), JSIConverter<${p.getCode('c++', { context: 'type-argument' })}>::toJSI(runtime, ${cppGetter}));` + const cppPropertyType = p.getCode('c++', { context: 'type-argument' }) + return ` +object.setProperty(runtime, PropNameIDCache::get(runtime, "${p.name}"), JSIConverter<${cppPropertyType}>::toJSI(runtime, ${indent(cppGetter, '')})); +`.trim() }) const cppPropertyChecks = swiftType.properties.map( (p) => @@ -107,7 +120,7 @@ public struct ${structType.structName}${conformancePrefix}${protocolNames.join(' ) const jsToSwiftConverterCode = ` auto object = value.asObject(runtime); -${cppInitializerCode} +${indent(jsToSwiftInitializerCode, '')} `.trim() const swiftToJSConverterCode = ` jsi::Object object(runtime); @@ -159,11 +172,16 @@ export function createSwiftStructCppConverter({ type, cppType }: SwiftStructCppC const cppInitializerValues = properties.map(({ native, swift }) => { const converter = getSwiftConverter({ cppType: native, swiftType: swift }) const cppGetter = createCppStructGetter({ property: swift, receiver: 'value' }) - return `${converter}::fromSwift(${cppGetter})` + return ` +${converter}::fromSwift(${indent(cppGetter, '')}) +`.trim() }) const swiftInitializerValues = properties.map(({ native, swift }) => { const converter = getSwiftConverter({ cppType: native, swiftType: swift }) - return { property: swift, value: `${converter}::toSwift(value.${native.escapedName})` } + const cppToSwiftPropertyCode = ` +${converter}::toSwift(value.${native.escapedName}) +`.trim() + return { property: swift, value: cppToSwiftPropertyCode } }) const swiftToCppConverterCode = ` return ${nativeStructType}(${cppInitializerValues.join(', ')}); @@ -194,15 +212,20 @@ function createCppStructInitializer({ type, properties }: CppStructInitializerOp const cppArguments = properties.map(({ property, value }) => { if (property.requiresOpaqueCxxBridge) { const name = `__${property.escapedName}` - cppInitializers.push(`auto ${name} = ${value};`) - return `std::addressof(${name})` + const cppToSwiftStorageCode = ` +auto ${name} = ${indent(value, '')}; +`.trim() + cppInitializers.push(cppToSwiftStorageCode) + return ` +std::addressof(${name}) +`.trim() } return value }) - const cppInitializerCode = ` + const cppToSwiftInitializerCode = ` return ${type}::init_cxx(${cppArguments.join(', ')}); `.trim() - return [...cppInitializers, cppInitializerCode].join('\n') + return [...cppInitializers, cppToSwiftInitializerCode].join('\n') } interface CppStructGetterOptions { @@ -222,7 +245,9 @@ ${receiver}.get${getterName}_cxx(${SWIFT_RESULT_STORAGE}); body: () => cppInitializeCode, }) } - return `${receiver}.get${getterName}_cxx()` + return ` +${receiver}.get${getterName}_cxx() +`.trim() } function getStructBridgeCppImports(type: SwiftStructType): SourceImport<'c++'>[] { From b721fc2a805eca5d5b5c1dff25df8402c21500d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 28 Oct 2026 11:50:07 +0100 Subject: [PATCH 5/5] fix(swift): keep callback struct bridges typed --- .../nitrogen/src/syntax/swift/SwiftStruct.ts | 164 +++++++++--------- .../src/syntax/swift/types/SwiftStructType.ts | 4 +- .../NitroTestAppleCxx/NitroTestAppleCxx.h | 5 + .../apple/c++/JsStyleStruct-Swift.cpp | 9 +- .../generated/apple/swift/JsStyleStruct.swift | 8 +- 5 files changed, 95 insertions(+), 95 deletions(-) diff --git a/packages/nitrogen/src/syntax/swift/SwiftStruct.ts b/packages/nitrogen/src/syntax/swift/SwiftStruct.ts index 34c3742b7d..f61ccf0e4f 100644 --- a/packages/nitrogen/src/syntax/swift/SwiftStruct.ts +++ b/packages/nitrogen/src/syntax/swift/SwiftStruct.ts @@ -5,14 +5,12 @@ import { getTypeAs } from '../types/getTypeAs.js' import type { Type } from '../types/Type.js' import { capitalizeName, indent } from '../../utils.js' import { createFileMetadataString } from '../helpers.js' -import type { SourceFile, SourceImport } from '../SourceFile.js' +import type { SourceFile } from '../SourceFile.js' import type { SwiftStructType } from './types/SwiftStructType.js' import { getImportsCode } from '../getImportCode.js' import { getProtocolConformanceImports, getSwiftImports } from './getSwiftImports.js' -import { SWIFT_RESULT_STORAGE } from './SwiftMethodHelpers.js' -import { SwiftCxxBorrowedPointerType } from './types/SwiftCxxBorrowedPointerType.js' -import { SwiftCxxOutPointerType } from './types/SwiftCxxOutPointerType.js' -import { SwiftNamedType } from './types/SwiftNamedType.js' +import type { CppHelper } from '../c++/CppHelper.js' +import type { SwiftNamedType } from './types/SwiftNamedType.js' export function createSwiftStructBridge(swiftType: SwiftStructType): SourceFile[] { const structType = swiftType.type @@ -24,42 +22,37 @@ export function createSwiftStructBridge(swiftType: SwiftStructType): SourceFile[ .map((p) => `${p.escapedName}: ${p.getCode('swift', { context: 'property' })}`) .join(', ') const swiftPropertyInitializers = structType.properties.map((p) => `self.${p.escapedName} = ${p.escapedName}`) - // TODO: Restore direct struct bridge signatures and remove the opaque C++ adapters once our minimum - // Xcode includes https://github.com/swiftlang/swift/pull/93321, - // which fixes https://github.com/swiftlang/swift/issues/93318. const swiftBridgeProperties = swiftType.properties.map((p) => { + let propertyType: string + let propertyValue: string if (p.requiresOpaqueCxxBridge) { - const pointerType = new SwiftCxxBorrowedPointerType(p) - return new SwiftNamedType(p.name, pointerType) + propertyType = getStructPropertyWrapperName({ type: swiftType, property: p, language: 'swift' }) + propertyValue = p.toSwift(`${p.escapedName}.value`) + } else { + propertyType = p.getCode('swift', { context: 'property' }) + propertyValue = p.toSwift(p.escapedName) } - return p + return { name: p.escapedName, type: propertyType, value: propertyValue } }) - const swiftBridgeInitializerParameters = swiftBridgeProperties - .map((p) => `_ ${p.escapedName}: ${p.getCode('swift', { context: 'property' })}`) - .join(', ') - const swiftBridgeInitializerArguments = swiftBridgeProperties.map((p) => `${p.escapedName}: ${p.toSwift(p.escapedName)}`).join(', ') + const swiftBridgeInitializerParameters = swiftBridgeProperties.map((p) => `_ ${p.name}: ${p.type}`).join(', ') + const swiftBridgeInitializerArguments = swiftBridgeProperties.map((p) => `${p.name}: ${p.value}`).join(', ') const swiftBridgeGetters = swiftType.properties.map((p) => { - const swiftGetterValue = p.fromSwift(`self.${p.escapedName}`) - let swiftGetterParameters: string + const swiftToCppValue = p.fromSwift(`self.${p.escapedName}`) let swiftGetterReturnType: string - let swiftGetterBody: string + let swiftGetterValue: string if (p.requiresOpaqueCxxBridge) { - const returnType = new SwiftCxxOutPointerType(p) - swiftGetterParameters = ` -_ ${SWIFT_RESULT_STORAGE}: ${returnType.getCode('swift', { context: 'parameter' })} -`.trim() - swiftGetterReturnType = 'Void' - swiftGetterBody = returnType.initialize('swift', SWIFT_RESULT_STORAGE, swiftGetterValue) + swiftGetterReturnType = getStructPropertyWrapperName({ type: swiftType, property: p, language: 'swift' }) + swiftGetterValue = `${swiftGetterReturnType}(${swiftToCppValue})` } else { - swiftGetterParameters = '' swiftGetterReturnType = p.getCode('swift', { context: 'return' }) - swiftGetterBody = ` + swiftGetterValue = swiftToCppValue + } + const swiftGetterBody = ` return ${indent(swiftGetterValue, '')} `.trim() - } return ` @_spi(NitroCxx) -public borrowing func get${capitalizeName(p.escapedName)}_cxx(${swiftGetterParameters}) -> ${swiftGetterReturnType} { +public borrowing func get${capitalizeName(p.escapedName)}_cxx() -> ${swiftGetterReturnType} { ${indent(swiftGetterBody, ' ')} } `.trim() @@ -104,11 +97,10 @@ public struct ${structType.structName}${conformancePrefix}${protocolNames.join(' const jsToSwiftPropertyCode = ` JSIConverter<${cppPropertyType}>::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "${p.name}"))) `.trim() - return { property: p, value: jsToSwiftPropertyCode } + return wrapCppStructProperty({ type: swiftType, property: p, value: jsToSwiftPropertyCode }) }) - const jsToSwiftInitializerCode = createCppStructInitializer({ type: cppStructType, properties: cppInitializerArguments }) const cppPropertyConversions = swiftType.properties.map((p) => { - const cppGetter = createCppStructGetter({ property: p, receiver: 'value' }) + const cppGetter = getCppStructProperty(p) const cppPropertyType = p.getCode('c++', { context: 'type-argument' }) return ` object.setProperty(runtime, PropNameIDCache::get(runtime, "${p.name}"), JSIConverter<${cppPropertyType}>::toJSI(runtime, ${indent(cppGetter, '')})); @@ -120,7 +112,7 @@ object.setProperty(runtime, PropNameIDCache::get(runtime, "${p.name}"), JSIConve ) const jsToSwiftConverterCode = ` auto object = value.asObject(runtime); -${indent(jsToSwiftInitializerCode, '')} +return ${cppStructType}::init_cxx(${cppInitializerArguments.join(', ')}); `.trim() const swiftToJSConverterCode = ` jsi::Object object(runtime); @@ -147,7 +139,6 @@ return ${cppPropertyChecks.join(' && ')}; space: 'system', }, }, - ...getStructBridgeCppImports(swiftType), ], fromJSI: jsToSwiftConverterCode, toJSI: swiftToJSConverterCode, @@ -171,7 +162,7 @@ export function createSwiftStructCppConverter({ type, cppType }: SwiftStructCppC const nativeStructType = structType.getCode('c++', { context: 'value', fullyQualified: true }) const cppInitializerValues = properties.map(({ native, swift }) => { const converter = getSwiftConverter({ cppType: native, swiftType: swift }) - const cppGetter = createCppStructGetter({ property: swift, receiver: 'value' }) + const cppGetter = getCppStructProperty(swift) return ` ${converter}::fromSwift(${indent(cppGetter, '')}) `.trim() @@ -181,12 +172,14 @@ ${converter}::fromSwift(${indent(cppGetter, '')}) const cppToSwiftPropertyCode = ` ${converter}::toSwift(value.${native.escapedName}) `.trim() - return { property: swift, value: cppToSwiftPropertyCode } + return wrapCppStructProperty({ type: type, property: swift, value: cppToSwiftPropertyCode }) }) const swiftToCppConverterCode = ` return ${nativeStructType}(${cppInitializerValues.join(', ')}); `.trim() - const cppToSwiftConverterCode = createCppStructInitializer({ type: 'SwiftType', properties: swiftInitializerValues }) + const cppToSwiftConverterCode = ` +return SwiftType::init_cxx(${swiftInitializerValues.join(', ')}); +`.trim() const dependencies = properties.map(({ native, swift }) => swift.getCppConverter(native)) const name = `${type.name}+${type.moduleName}` return createSwiftConverter({ @@ -197,66 +190,73 @@ return ${nativeStructType}(${cppInitializerValues.join(', ')}); fromSwift: swiftToCppConverterCode, toSwift: cppToSwiftConverterCode, dependencies: dependencies, - imports: getStructBridgeCppImports(type), }) } -interface CppStructInitializerOptions { - type: string - properties: { property: SwiftNamedType; value: string }[] +interface StructPropertyWrapperOptions { + type: SwiftStructType + property: SwiftNamedType + language: 'swift' | 'c++' } -/** Keeps borrowed C++ values alive until Swift has copied them into the struct. */ -function createCppStructInitializer({ type, properties }: CppStructInitializerOptions): string { - const cppInitializers: string[] = [] - const cppArguments = properties.map(({ property, value }) => { - if (property.requiresOpaqueCxxBridge) { - const name = `__${property.escapedName}` - const cppToSwiftStorageCode = ` -auto ${name} = ${indent(value, '')}; -`.trim() - cppInitializers.push(cppToSwiftStorageCode) - return ` -std::addressof(${name}) -`.trim() - } - return value - }) - const cppToSwiftInitializerCode = ` -return ${type}::init_cxx(${cppArguments.join(', ')}); -`.trim() - return [...cppInitializers, cppToSwiftInitializerCode].join('\n') +function getStructPropertyWrapperName({ type, property, language }: StructPropertyWrapperOptions): string { + const namespace = type.config.getSwiftBridgeNamespace(language) + const name = `${type.name}_${property.escapedName}_cxx` + switch (language) { + case 'swift': + return `${namespace}.${name}` + case 'c++': + return `${namespace}::${name}` + } } -interface CppStructGetterOptions { +interface WrapCppStructPropertyOptions { + type: SwiftStructType property: SwiftNamedType - receiver: string + value: string } -function createCppStructGetter({ property, receiver }: CppStructGetterOptions): string { - const getterName = capitalizeName(property.escapedName) +function wrapCppStructProperty({ type, property, value }: WrapCppStructPropertyOptions): string { if (property.requiresOpaqueCxxBridge) { - const returnType = new SwiftCxxOutPointerType(property) - const cppInitializeCode = ` -${receiver}.get${getterName}_cxx(${SWIFT_RESULT_STORAGE}); + const wrapper = getStructPropertyWrapperName({ type: type, property: property, language: 'c++' }) + return ` +${wrapper}(${indent(value, '')}) `.trim() - return returnType.allocate('c++', { - storage: SWIFT_RESULT_STORAGE, - body: () => cppInitializeCode, - }) } - return ` -${receiver}.get${getterName}_cxx() -`.trim() + return value } -function getStructBridgeCppImports(type: SwiftStructType): SourceImport<'c++'>[] { - const hasOpaqueProperties = type.properties.some((property) => property.requiresOpaqueCxxBridge) - if (hasOpaqueProperties) { - return [ - { language: 'c++', declaration: { header: 'NitroModules/SwiftResult.hpp', space: 'system' } }, - { language: 'c++', declaration: { header: 'memory', space: 'system' } }, - ] +function getCppStructProperty(property: SwiftNamedType): string { + const getterName = capitalizeName(property.escapedName) + const cppGetter = `value.get${getterName}_cxx()` + if (property.requiresOpaqueCxxBridge) { + return `${cppGetter}.value` } - return [] + return cppGetter +} + +export function createSwiftStructPropertyHelpers(type: SwiftStructType): CppHelper[] { + if (type.config.isExternalConfig) return [] + const properties = type.properties.filter((property) => property.requiresOpaqueCxxBridge) + return properties.map((property) => { + const name = `${type.name}_${property.escapedName}_cxx` + const cppType = property.getCode('c++', { context: 'value' }) + // TODO: Remove these module-owned wrappers once our minimum Xcode includes + // https://github.com/swiftlang/swift/pull/93321, fixing https://github.com/swiftlang/swift/issues/93318. + const cppWrapperCode = ` +struct ${name} final { + ${cppType} value; + explicit ${name}(${cppType} value) : value(std::move(value)) {} +}; +`.trim() + return { + header: { + code: cppWrapperCode, + requiredImports: [ + ...property.getRequiredImports('c++', 'declaration'), + { language: 'c++', declaration: { header: 'utility', space: 'system' } }, + ], + }, + } + }) } diff --git a/packages/nitrogen/src/syntax/swift/types/SwiftStructType.ts b/packages/nitrogen/src/syntax/swift/types/SwiftStructType.ts index dc7bc69708..4f9b9a62ef 100644 --- a/packages/nitrogen/src/syntax/swift/types/SwiftStructType.ts +++ b/packages/nitrogen/src/syntax/swift/types/SwiftStructType.ts @@ -2,7 +2,7 @@ import type { Type, GetCodeOptions, TypeContext } from '../../types/Type.js' import type { CppHelper } from '../../c++/CppHelper.js' import type { SourceFile } from '../../SourceFile.js' import { StructType } from '../../types/StructType.js' -import { createSwiftStructBridge, createSwiftStructCppConverter } from '../SwiftStruct.js' +import { createSwiftStructBridge, createSwiftStructCppConverter, createSwiftStructPropertyHelpers } from '../SwiftStruct.js' import { wrapSwiftType } from '../wrapSwiftType.js' import { NitroConfig } from '../../../config/NitroConfig.js' import type { Language } from '../../../getPlatformSpecs.js' @@ -102,7 +102,7 @@ export class SwiftStructType extends StructType implements SwiftType { } get requiredCppHelpers(): CppHelper[] { - return this.properties.flatMap((p) => p.requiredCppHelpers) + return [...this.properties.flatMap((p) => p.requiredCppHelpers), ...createSwiftStructPropertyHelpers(this)] } override getRequiredImports(language: Language, context: 'declaration' | 'implementation' = 'implementation'): SourceImport[] { diff --git a/tests/standalone/nitro-test/nitrogen/generated/apple/NitroTestAppleCxx/include/NitroTestAppleCxx/NitroTestAppleCxx.h b/tests/standalone/nitro-test/nitrogen/generated/apple/NitroTestAppleCxx/include/NitroTestAppleCxx/NitroTestAppleCxx.h index 87c691d3a4..caf71cc964 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/apple/NitroTestAppleCxx/include/NitroTestAppleCxx/NitroTestAppleCxx.h +++ b/tests/standalone/nitro-test/nitrogen/generated/apple/NitroTestAppleCxx/include/NitroTestAppleCxx/NitroTestAppleCxx.h @@ -151,6 +151,7 @@ namespace margelo::nitro::test::external { class HybridSomeExternalObjectSpecSwi namespace margelo::nitro::test { class HybridSomeExternalObjectChildSpecSwift; } // Forward declaration of `HybridTestObjectSwiftKotlinSpecSwift` to properly resolve imports. namespace margelo::nitro::test { class HybridTestObjectSwiftKotlinSpecSwift; } +#include #include // Forward declaration of `HybridSharedSpecializationsSpecSwift` to properly resolve imports. namespace margelo::nitro::test::external { class HybridSharedSpecializationsSpecSwift; } @@ -262,6 +263,10 @@ namespace margelo::nitro::test::swift_bridge { using Callback_uint64_t_uint64_t = Callback; Result callSync_Callback_uint64_t_uint64_t(const Callback_uint64_t_uint64_t& callback, uint64_t param0) noexcept; Callback_uint64_t_uint64_t create_Callback_uint64_t_uint64_t(std::function(uint64_t param0)> closure); + struct JsStyleStruct_onChanged_cxx final { + Callback value; + explicit JsStyleStruct_onChanged_cxx(Callback value) : value(std::move(value)) {} + }; using Result_double = Result; using Callback_double = Callback; Result callSync_Callback_double(const Callback_double& callback) noexcept; diff --git a/tests/standalone/nitro-test/nitrogen/generated/apple/c++/JsStyleStruct-Swift.cpp b/tests/standalone/nitro-test/nitrogen/generated/apple/c++/JsStyleStruct-Swift.cpp index dfd8e20876..58059b42c5 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/apple/c++/JsStyleStruct-Swift.cpp +++ b/tests/standalone/nitro-test/nitrogen/generated/apple/c++/JsStyleStruct-Swift.cpp @@ -8,23 +8,18 @@ #include #include #include -#include -#include namespace margelo::nitro { ::NitroTest::JsStyleStruct JSIConverter<::NitroTest::JsStyleStruct>::fromJSI(jsi::Runtime& runtime, const jsi::Value& value) { auto object = value.asObject(runtime); - auto __onChanged = JSIConverter>::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "onChanged"))); - return ::NitroTest::JsStyleStruct::init_cxx(JSIConverter::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "value"))), std::addressof(__onChanged)); + return ::NitroTest::JsStyleStruct::init_cxx(JSIConverter::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "value"))), margelo::nitro::test::swift_bridge::JsStyleStruct_onChanged_cxx(JSIConverter>::fromJSI(runtime, object.getProperty(runtime, PropNameIDCache::get(runtime, "onChanged"))))); } jsi::Value JSIConverter<::NitroTest::JsStyleStruct>::toJSI(jsi::Runtime& runtime, const ::NitroTest::JsStyleStruct& value) { jsi::Object object(runtime); object.setProperty(runtime, PropNameIDCache::get(runtime, "value"), JSIConverter::toJSI(runtime, value.getValue_cxx())); - object.setProperty(runtime, PropNameIDCache::get(runtime, "onChanged"), JSIConverter>::toJSI(runtime, makeCxxValue>([&](void* __outResult) { - value.getOnChanged_cxx(__outResult); - }))); + object.setProperty(runtime, PropNameIDCache::get(runtime, "onChanged"), JSIConverter>::toJSI(runtime, value.getOnChanged_cxx().value)); return object; } diff --git a/tests/standalone/nitro-test/nitrogen/generated/apple/swift/JsStyleStruct.swift b/tests/standalone/nitro-test/nitrogen/generated/apple/swift/JsStyleStruct.swift index dbcd455096..16a7b8fe8e 100644 --- a/tests/standalone/nitro-test/nitrogen/generated/apple/swift/JsStyleStruct.swift +++ b/tests/standalone/nitro-test/nitrogen/generated/apple/swift/JsStyleStruct.swift @@ -20,8 +20,8 @@ public struct JsStyleStruct: Sendable { } @_spi(NitroCxx) - public static func init_cxx(_ value: Double, _ onChanged: UnsafeRawPointer) -> Self { - return Self(value: value, onChanged: onChanged.load(as: margelo.nitro.test.swift_bridge.Callback_void_double.self)) + public static func init_cxx(_ value: Double, _ onChanged: margelo.nitro.test.swift_bridge.JsStyleStruct_onChanged_cxx) -> Self { + return Self(value: value, onChanged: onChanged.value) } @_spi(NitroCxx) @@ -30,7 +30,7 @@ public struct JsStyleStruct: Sendable { } @_spi(NitroCxx) - public borrowing func getOnChanged_cxx(_ __outResult: UnsafeMutableRawPointer) -> Void { - __outResult.initializeMemory(as: margelo.nitro.test.swift_bridge.Callback_void_double.self, repeating: self.onChanged.toCpp(), count: 1) + public borrowing func getOnChanged_cxx() -> margelo.nitro.test.swift_bridge.JsStyleStruct_onChanged_cxx { + return margelo.nitro.test.swift_bridge.JsStyleStruct_onChanged_cxx(self.onChanged.toCpp()) } }