Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 119 additions & 16 deletions packages/nitrogen/src/syntax/swift/SwiftStruct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
})
Expand Down Expand Up @@ -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) =>
Expand Down Expand Up @@ -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(', ')});
Expand All @@ -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' } },
],
},
}
})
}
4 changes: 2 additions & 2 deletions packages/nitrogen/src/syntax/swift/types/SwiftStructType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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[] {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading