diff --git a/packages/nitrogen/src/syntax/swift/SwiftStruct.ts b/packages/nitrogen/src/syntax/swift/SwiftStruct.ts index fbd21aac75..f61ccf0e4f 100644 --- a/packages/nitrogen/src/syntax/swift/SwiftStruct.ts +++ b/packages/nitrogen/src/syntax/swift/SwiftStruct.ts @@ -9,6 +9,8 @@ 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 type { CppHelper } from '../c++/CppHelper.js' +import type { SwiftNamedType } from './types/SwiftNamedType.js' export function createSwiftStructBridge(swiftType: SwiftStructType): SourceFile[] { const structType = swiftType.type @@ -20,16 +22,38 @@ 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' })}`) - .join(', ') - const swiftBridgeInitializerArguments = swiftType.properties.map((p) => `${p.escapedName}: ${p.toSwift(p.escapedName)}`).join(', ') + const swiftBridgeProperties = swiftType.properties.map((p) => { + let propertyType: string + let propertyValue: string + if (p.requiresOpaqueCxxBridge) { + 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 { name: p.escapedName, type: propertyType, value: propertyValue } + }) + 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 swiftGetterBody = `return ${p.fromSwift(`self.${p.escapedName}`)}` + const swiftToCppValue = p.fromSwift(`self.${p.escapedName}`) + let swiftGetterReturnType: string + let swiftGetterValue: string + if (p.requiresOpaqueCxxBridge) { + swiftGetterReturnType = getStructPropertyWrapperName({ type: swiftType, property: p, language: 'swift' }) + swiftGetterValue = `${swiftGetterReturnType}(${swiftToCppValue})` + } else { + swiftGetterReturnType = p.getCode('swift', { context: 'return' }) + swiftGetterValue = swiftToCppValue + } + const swiftGetterBody = ` +return ${indent(swiftGetterValue, '')} +`.trim() return ` @_spi(NitroCxx) -public borrowing func get${capitalizeName(p.escapedName)}_cxx() -> ${p.getCode('swift', { context: 'return' })} { - ${swiftGetterBody} +public borrowing func get${capitalizeName(p.escapedName)}_cxx() -> ${swiftGetterReturnType} { + ${indent(swiftGetterBody, ' ')} } `.trim() }) @@ -68,13 +92,19 @@ 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 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 wrapCppStructProperty({ type: swiftType, property: p, value: jsToSwiftPropertyCode }) + }) 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 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, '')})); +`.trim() }) const cppPropertyChecks = swiftType.properties.map( (p) => @@ -132,12 +162,17 @@ 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 getterName = `get${capitalizeName(swift.escapedName)}_cxx` - return `${converter}::fromSwift(value.${getterName}())` + const cppGetter = getCppStructProperty(swift) + return ` +${converter}::fromSwift(${indent(cppGetter, '')}) +`.trim() }) const swiftInitializerValues = properties.map(({ native, swift }) => { const converter = getSwiftConverter({ cppType: native, swiftType: swift }) - return `${converter}::toSwift(value.${native.escapedName})` + const cppToSwiftPropertyCode = ` +${converter}::toSwift(value.${native.escapedName}) +`.trim() + return wrapCppStructProperty({ type: type, property: swift, value: cppToSwiftPropertyCode }) }) const swiftToCppConverterCode = ` return ${nativeStructType}(${cppInitializerValues.join(', ')}); @@ -157,3 +192,71 @@ return SwiftType::init_cxx(${swiftInitializerValues.join(', ')}); dependencies: dependencies, }) } + +interface StructPropertyWrapperOptions { + type: SwiftStructType + property: SwiftNamedType + language: 'swift' | 'c++' +} + +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 WrapCppStructPropertyOptions { + type: SwiftStructType + property: SwiftNamedType + value: string +} + +function wrapCppStructProperty({ type, property, value }: WrapCppStructPropertyOptions): string { + if (property.requiresOpaqueCxxBridge) { + const wrapper = getStructPropertyWrapperName({ type: type, property: property, language: 'c++' }) + return ` +${wrapper}(${indent(value, '')}) +`.trim() + } + return value +} + +function getCppStructProperty(property: SwiftNamedType): string { + const getterName = capitalizeName(property.escapedName) + const cppGetter = `value.get${getterName}_cxx()` + if (property.requiresOpaqueCxxBridge) { + return `${cppGetter}.value` + } + 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 8cd1bbee06..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 @@ -13,13 +13,13 @@ 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")))); + 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, value.getOnChanged_cxx())); + 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 2467139a33..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: margelo.nitro.test.swift_bridge.Callback_void_double) -> Self { - return Self(value: value, onChanged: onChanged) + 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() -> margelo.nitro.test.swift_bridge.Callback_void_double { - return self.onChanged.toCpp() + 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()) } }