Skip to content

Commit

Permalink
feat: Implement proper type-erasure inheritance in Swift (#427)
Browse files Browse the repository at this point in the history
* fix: Move `getCxxPart()` into Cxx wrapper itself to make it overridable

* Update SwiftHybridObjectBridge.ts

* Update SwiftHybridObjectBridge.ts

* fix: Move `getCxxWrapper()` into Swift class

* fix: Add base types to `HybridObjectType`

* feat: Add upcast helper

* fix: Use clear name to avoid confusion (`upcast()` -> `upcast_Child_to_Base()`)

* Update Testers.ts

* fix: Guard check

* perf: Cache `getCxxWrapper()`
  • Loading branch information
mrousavy authored Dec 17, 2024
1 parent e8b01d0 commit 50e0de3
Show file tree
Hide file tree
Showing 21 changed files with 328 additions and 96 deletions.
2 changes: 1 addition & 1 deletion packages/nitrogen/src/syntax/c++/CppHybridObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function createCppHybridObject(spec: HybridObjectSpec): SourceFile[] {

const bases = ['public virtual HybridObject']
for (const base of spec.baseTypes) {
const hybridObject = new HybridObjectType(base.name, spec.language)
const hybridObject = new HybridObjectType(base)
bases.push(`public virtual ${getHybridObjectName(base.name).HybridTSpec}`)
const imports = hybridObject.getRequiredImports()
cppForwardDeclarations.push(
Expand Down
7 changes: 6 additions & 1 deletion packages/nitrogen/src/syntax/createType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,12 @@ export function createType(
} else if (extendsHybridObject(type, true)) {
// It is another HybridObject being referenced!
const typename = type.getSymbolOrThrow().getEscapedName()
return new HybridObjectType(typename, language)
const baseTypes = type
.getBaseTypes()
.filter((t) => extendsHybridObject(t, true))
.map((b) => createType(language, b, false))
const baseHybrids = baseTypes.filter((b) => b instanceof HybridObjectType)
return new HybridObjectType(typename, language, baseHybrids)
} else if (isDirectlyHybridObject(type)) {
// It is a HybridObject directly/literally. Base type
return new HybridObjectBaseType()
Expand Down
7 changes: 2 additions & 5 deletions packages/nitrogen/src/syntax/swift/SwiftCxxBridgedType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,15 +583,12 @@ case ${i}:
}
case 'hybrid-object': {
const bridge = this.getBridgeOrThrow()
const name = getTypeHybridObjectName(this.type)
const makeFunc = `bridge.${bridge.funcName}`
switch (language) {
case 'swift':
return `
{ () -> bridge.${bridge.specializationName} in
let __cxxWrapped = ${name.HybridTSpecCxx}(${swiftParameterName})
let __pointer = __cxxWrapped.toUnsafe()
return ${makeFunc}(__pointer)
let __cxxWrapped = ${swiftParameterName}.getCxxWrapper()
return __cxxWrapped.getCxxPart()
}()`.trim()
default:
return swiftParameterName
Expand Down
28 changes: 28 additions & 0 deletions packages/nitrogen/src/syntax/swift/SwiftCxxTypeHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ function createCxxHybridObjectSwiftHelper(
const swiftWrappingType = NitroConfig.getCxxNamespace('c++', HybridTSpecSwift)
const swiftPartType = `${modulename}::${HybridTSpecCxx}`
const name = escapeCppName(actualType)

const upcastHelpers = type.baseTypes.map((base) =>
createCxxUpcastHelper(base, type)
)

return {
cxxType: actualType,
funcName: `create_${name}`,
Expand Down Expand Up @@ -129,6 +134,29 @@ void* _Nonnull get_${name}(${name} cppType) {
},
],
},
dependencies: [...upcastHelpers],
}
}

function createCxxUpcastHelper(
baseType: HybridObjectType,
childType: HybridObjectType
): SwiftCxxHelper {
const cppBaseType = baseType.getCode('c++')
const cppChildType = childType.getCode('c++')
const funcName = escapeCppName(
`upcast_${childType.hybridObjectName}_to_${baseType.hybridObjectName}`
)
return {
cxxType: cppBaseType,
funcName: funcName,
specializationName: funcName,
cxxHeader: {
code: `
inline ${cppBaseType} ${funcName}(${cppChildType} child) { return child; }
`.trim(),
requiredIncludes: [],
},
dependencies: [],
}
}
Expand Down
22 changes: 21 additions & 1 deletion packages/nitrogen/src/syntax/swift/SwiftHybridObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,30 @@ export function createSwiftHybridObject(spec: HybridObjectSpec): SourceFile[] {
classBaseClasses.push(`${baseName.HybridTSpec}_base`)
}

const hasBaseClass = classBaseClasses.length > 0
const baseMembers: string[] = []
if (classBaseClasses.length === 0) {
baseMembers.push(`private weak var cxxWrapper: ${name.HybridTSpecCxx}? = nil`)
baseMembers.push(
`
public ${hasBaseClass ? 'override func' : 'func'} getCxxWrapper() -> ${name.HybridTSpecCxx} {
#if DEBUG
guard self is ${name.HybridTSpec} else {
fatalError("\`self\` is not a \`${name.HybridTSpec}\`! Did you accidentally inherit from \`${name.HybridTSpec}_base\` instead of \`${name.HybridTSpec}\`?")
}
#endif
if let cxxWrapper = self.cxxWrapper {
return cxxWrapper
} else {
let cxxWrapper = ${name.HybridTSpecCxx}(self as! ${name.HybridTSpec})
self.cxxWrapper = cxxWrapper
return cxxWrapper
}
}`.trim()
)
if (!hasBaseClass) {
// It doesn't have a base class - implement hybridContext
classBaseClasses.push('HybridObjectSpec')

baseMembers.push(`public var hybridContext = margelo.nitro.HybridContext()`)
baseMembers.push(`public var memorySize: Int { return getSizeOf(self) }`)
}
Expand Down
47 changes: 45 additions & 2 deletions packages/nitrogen/src/syntax/swift/SwiftHybridObjectBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import { SwiftCxxBridgedType } from './SwiftCxxBridgedType.js'
import type { Property } from '../Property.js'
import { indent } from '../../utils.js'
import type { Method } from '../Method.js'
import { createFileMetadataString, isNotDuplicate } from '../helpers.js'
import {
createFileMetadataString,
escapeCppName,
isNotDuplicate,
} from '../helpers.js'
import type { SourceFile } from '../SourceFile.js'
import { getHybridObjectName } from '../getHybridObjectName.js'
import { getForwardDeclaration } from '../c++/getForwardDeclaration.js'
import { NitroConfig } from '../../config/NitroConfig.js'
import { includeHeader, includeNitroHeader } from '../c++/includeNitroHeader.js'
import { getUmbrellaHeaderName } from '../../autolinking/ios/createSwiftUmbrellaHeader.js'
import { HybridObjectType } from '../types/HybridObjectType.js'

export function getBridgeNamespace() {
return NitroConfig.getCxxNamespace('swift', 'bridge', 'swift')
Expand Down Expand Up @@ -42,6 +47,34 @@ export function createSwiftHybridObjectCxxBridge(
})
const hasBase = baseClasses.length > 0

const bridgedType = new SwiftCxxBridgedType(new HybridObjectType(spec))
const bridge = bridgedType.getRequiredBridge()
if (bridge == null) throw new Error(`HybridObject Type should have a bridge!`)

const baseGetCxxPartOverrides = spec.baseTypes.map((base) => {
const baseHybridObject = new HybridObjectType(base)
const bridgedBase = new SwiftCxxBridgedType(baseHybridObject)
const baseBridge = bridgedBase.getRequiredBridge()
if (baseBridge == null)
throw new Error(`HybridObject ${base.name}'s bridge cannot be null!`)

const upcastBridge = bridge.dependencies.find(
(b) =>
b.funcName.includes(escapeCppName(spec.name)) &&
b.funcName.includes(escapeCppName(base.name))
)
if (upcastBridge == null)
throw new Error(
`HybridObject ${spec.name}'s upcast-bridge cannot be found! ${JSON.stringify(baseBridge)}`
)

return `
public override func getCxxPart() -> bridge.${baseBridge.specializationName} {
let ownCxxPart = bridge.${bridge.funcName}(self.toUnsafe())
return bridge.${upcastBridge.funcName}(ownCxxPart)
}`.trim()
})

const swiftCxxWrapperCode = `
${createFileMetadataString(`${name.HybridTSpecCxx}.swift`)}
Expand Down Expand Up @@ -74,7 +107,7 @@ ${hasBase ? `public class ${name.HybridTSpecCxx} : ${baseClasses.join(', ')}` :
* Create a new \`${name.HybridTSpecCxx}\` that wraps the given \`${name.HybridTSpec}\`.
* All properties and methods bridge to C++ types.
*/
public init(_ implementation: some ${name.HybridTSpec}) {
public init(_ implementation: any ${name.HybridTSpec}) {
self.__implementation = implementation
${hasBase ? 'super.init(implementation)' : '/* no base class */'}
}
Expand Down Expand Up @@ -104,6 +137,16 @@ ${hasBase ? `public class ${name.HybridTSpecCxx} : ${baseClasses.join(', ')}` :
return Unmanaged<${name.HybridTSpecCxx}>.fromOpaque(pointer).takeRetainedValue()
}
/**
* Gets (or creates) the C++ part of this Hybrid Object.
* The C++ part is a \`${bridge.cxxType}\`.
*/
public func getCxxPart() -> bridge.${bridge.specializationName} {
return bridge.${bridge.funcName}(self.toUnsafe())
}
${indent(baseGetCxxPartOverrides.join('\n'), ' ')}
/**
* Contains a (weak) reference to the C++ HybridObject to cache it.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function createSwiftHybridObjectRegistration({
const { HybridTSpecCxx, HybridTSpecSwift, HybridTSpec } =
getHybridObjectName(hybridObjectName)

const type = new HybridObjectType(hybridObjectName, 'swift')
const type = new HybridObjectType(hybridObjectName, 'swift', [])
const bridge = new SwiftCxxBridgedType(type)

return {
Expand Down
33 changes: 30 additions & 3 deletions packages/nitrogen/src/syntax/types/HybridObjectType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,43 @@ import { NitroConfig } from '../../config/NitroConfig.js'
import type { Language } from '../../getPlatformSpecs.js'
import { getForwardDeclaration } from '../c++/getForwardDeclaration.js'
import { getHybridObjectName } from '../getHybridObjectName.js'
import type { HybridObjectSpec } from '../HybridObjectSpec.js'
import type { SourceFile, SourceImport } from '../SourceFile.js'
import type { Type, TypeKind } from './Type.js'

export class HybridObjectType implements Type {
readonly hybridObjectName: string
readonly implementationLanguage: Language
readonly baseTypes: HybridObjectType[]

constructor(hybridObjectName: string, implementationLanguage: Language) {
this.hybridObjectName = hybridObjectName
this.implementationLanguage = implementationLanguage
constructor(
hybridObjectName: string,
implementationLanguage: Language,
baseTypes: HybridObjectType[]
)
constructor(spec: HybridObjectSpec)
constructor(
...args:
| [
hybridObjectName: string,
implementationLanguage: Language,
baseTypes: HybridObjectType[],
]
| [HybridObjectSpec]
) {
if (args.length === 1) {
const [spec] = args

this.hybridObjectName = spec.name
this.implementationLanguage = spec.language
this.baseTypes = spec.baseTypes.map((b) => new HybridObjectType(b))
} else {
const [hybridObjectName, implementationLanguage, baseTypes] = args

this.hybridObjectName = hybridObjectName
this.implementationLanguage = implementationLanguage
this.baseTypes = baseTypes
}

if (this.hybridObjectName.startsWith('__')) {
throw new Error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,22 +66,6 @@ namespace margelo::nitro::image::bridge::swift {
return swiftPart.toUnsafe();
}

// pragma MARK: std::shared_ptr<margelo::nitro::image::HybridChildSpec>
std::shared_ptr<margelo::nitro::image::HybridChildSpec> create_std__shared_ptr_margelo__nitro__image__HybridChildSpec_(void* _Nonnull swiftUnsafePointer) {
NitroImage::HybridChildSpec_cxx swiftPart = NitroImage::HybridChildSpec_cxx::fromUnsafe(swiftUnsafePointer);
return HybridContext::getOrCreate<margelo::nitro::image::HybridChildSpecSwift>(swiftPart);
}
void* _Nonnull get_std__shared_ptr_margelo__nitro__image__HybridChildSpec_(std__shared_ptr_margelo__nitro__image__HybridChildSpec_ cppType) {
std::shared_ptr<margelo::nitro::image::HybridChildSpecSwift> swiftWrapper = std::dynamic_pointer_cast<margelo::nitro::image::HybridChildSpecSwift>(cppType);
#ifdef NITRO_DEBUG
if (swiftWrapper == nullptr) [[unlikely]] {
throw std::runtime_error("Class \"HybridChildSpec\" is not implemented in Swift!");
}
#endif
NitroImage::HybridChildSpec_cxx swiftPart = swiftWrapper->getSwiftPart();
return swiftPart.toUnsafe();
}

// pragma MARK: std::shared_ptr<margelo::nitro::image::HybridBaseSpec>
std::shared_ptr<margelo::nitro::image::HybridBaseSpec> create_std__shared_ptr_margelo__nitro__image__HybridBaseSpec_(void* _Nonnull swiftUnsafePointer) {
NitroImage::HybridBaseSpec_cxx swiftPart = NitroImage::HybridBaseSpec_cxx::fromUnsafe(swiftUnsafePointer);
Expand All @@ -97,5 +81,21 @@ namespace margelo::nitro::image::bridge::swift {
NitroImage::HybridBaseSpec_cxx swiftPart = swiftWrapper->getSwiftPart();
return swiftPart.toUnsafe();
}

// pragma MARK: std::shared_ptr<margelo::nitro::image::HybridChildSpec>
std::shared_ptr<margelo::nitro::image::HybridChildSpec> create_std__shared_ptr_margelo__nitro__image__HybridChildSpec_(void* _Nonnull swiftUnsafePointer) {
NitroImage::HybridChildSpec_cxx swiftPart = NitroImage::HybridChildSpec_cxx::fromUnsafe(swiftUnsafePointer);
return HybridContext::getOrCreate<margelo::nitro::image::HybridChildSpecSwift>(swiftPart);
}
void* _Nonnull get_std__shared_ptr_margelo__nitro__image__HybridChildSpec_(std__shared_ptr_margelo__nitro__image__HybridChildSpec_ cppType) {
std::shared_ptr<margelo::nitro::image::HybridChildSpecSwift> swiftWrapper = std::dynamic_pointer_cast<margelo::nitro::image::HybridChildSpecSwift>(cppType);
#ifdef NITRO_DEBUG
if (swiftWrapper == nullptr) [[unlikely]] {
throw std::runtime_error("Class \"HybridChildSpec\" is not implemented in Swift!");
}
#endif
NitroImage::HybridChildSpec_cxx swiftPart = swiftWrapper->getSwiftPart();
return swiftPart.toUnsafe();
}

} // namespace margelo::nitro::image::bridge::swift
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,14 @@ namespace margelo::nitro::image::bridge::swift {
return std::make_shared<Func_void_std__shared_ptr_ArrayBuffer__Wrapper>(value);
}

// pragma MARK: std::shared_ptr<margelo::nitro::image::HybridBaseSpec>
/**
* Specialized version of `std::shared_ptr<margelo::nitro::image::HybridBaseSpec>`.
*/
using std__shared_ptr_margelo__nitro__image__HybridBaseSpec_ = std::shared_ptr<margelo::nitro::image::HybridBaseSpec>;
std::shared_ptr<margelo::nitro::image::HybridBaseSpec> create_std__shared_ptr_margelo__nitro__image__HybridBaseSpec_(void* _Nonnull swiftUnsafePointer);
void* _Nonnull get_std__shared_ptr_margelo__nitro__image__HybridBaseSpec_(std__shared_ptr_margelo__nitro__image__HybridBaseSpec_ cppType);

// pragma MARK: std::shared_ptr<margelo::nitro::image::HybridChildSpec>
/**
* Specialized version of `std::shared_ptr<margelo::nitro::image::HybridChildSpec>`.
Expand All @@ -614,11 +622,6 @@ namespace margelo::nitro::image::bridge::swift {
void* _Nonnull get_std__shared_ptr_margelo__nitro__image__HybridChildSpec_(std__shared_ptr_margelo__nitro__image__HybridChildSpec_ cppType);

// pragma MARK: std::shared_ptr<margelo::nitro::image::HybridBaseSpec>
/**
* Specialized version of `std::shared_ptr<margelo::nitro::image::HybridBaseSpec>`.
*/
using std__shared_ptr_margelo__nitro__image__HybridBaseSpec_ = std::shared_ptr<margelo::nitro::image::HybridBaseSpec>;
std::shared_ptr<margelo::nitro::image::HybridBaseSpec> create_std__shared_ptr_margelo__nitro__image__HybridBaseSpec_(void* _Nonnull swiftUnsafePointer);
void* _Nonnull get_std__shared_ptr_margelo__nitro__image__HybridBaseSpec_(std__shared_ptr_margelo__nitro__image__HybridBaseSpec_ cppType);
inline std::shared_ptr<margelo::nitro::image::HybridBaseSpec> upcast_Child_to_Base(std::shared_ptr<margelo::nitro::image::HybridChildSpec> child) { return child; }

} // namespace margelo::nitro::image::bridge::swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ public final class NitroImageAutolinking {
public static func createImageFactory() -> bridge.std__shared_ptr_margelo__nitro__image__HybridImageFactorySpec_ {
let hybridObject = HybridImageFactory()
return { () -> bridge.std__shared_ptr_margelo__nitro__image__HybridImageFactorySpec_ in
let __cxxWrapped = HybridImageFactorySpec_cxx(hybridObject)
let __pointer = __cxxWrapped.toUnsafe()
return bridge.create_std__shared_ptr_margelo__nitro__image__HybridImageFactorySpec_(__pointer)
let __cxxWrapped = hybridObject.getCxxWrapper()
return __cxxWrapped.getCxxPart()
}()
}

Expand All @@ -34,9 +33,8 @@ public final class NitroImageAutolinking {
public static func createTestObjectSwiftKotlin() -> bridge.std__shared_ptr_margelo__nitro__image__HybridTestObjectSwiftKotlinSpec_ {
let hybridObject = HybridTestObjectSwift()
return { () -> bridge.std__shared_ptr_margelo__nitro__image__HybridTestObjectSwiftKotlinSpec_ in
let __cxxWrapped = HybridTestObjectSwiftKotlinSpec_cxx(hybridObject)
let __pointer = __cxxWrapped.toUnsafe()
return bridge.create_std__shared_ptr_margelo__nitro__image__HybridTestObjectSwiftKotlinSpec_(__pointer)
let __cxxWrapped = hybridObject.getCxxWrapper()
return __cxxWrapped.getCxxPart()
}()
}

Expand All @@ -50,9 +48,8 @@ public final class NitroImageAutolinking {
public static func createBase() -> bridge.std__shared_ptr_margelo__nitro__image__HybridBaseSpec_ {
let hybridObject = HybridBase()
return { () -> bridge.std__shared_ptr_margelo__nitro__image__HybridBaseSpec_ in
let __cxxWrapped = HybridBaseSpec_cxx(hybridObject)
let __pointer = __cxxWrapped.toUnsafe()
return bridge.create_std__shared_ptr_margelo__nitro__image__HybridBaseSpec_(__pointer)
let __cxxWrapped = hybridObject.getCxxWrapper()
return __cxxWrapped.getCxxPart()
}()
}

Expand All @@ -66,9 +63,8 @@ public final class NitroImageAutolinking {
public static func createChild() -> bridge.std__shared_ptr_margelo__nitro__image__HybridChildSpec_ {
let hybridObject = HybridChild()
return { () -> bridge.std__shared_ptr_margelo__nitro__image__HybridChildSpec_ in
let __cxxWrapped = HybridChildSpec_cxx(hybridObject)
let __pointer = __cxxWrapped.toUnsafe()
return bridge.create_std__shared_ptr_margelo__nitro__image__HybridChildSpec_(__pointer)
let __cxxWrapped = hybridObject.getCxxWrapper()
return __cxxWrapped.getCxxPart()
}()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ public protocol HybridBaseSpec_protocol: AnyObject {

/// See ``HybridBaseSpec``
public class HybridBaseSpec_base: HybridObjectSpec {
private weak var cxxWrapper: HybridBaseSpec_cxx? = nil
public func getCxxWrapper() -> HybridBaseSpec_cxx {
#if DEBUG
guard self is HybridBaseSpec else {
fatalError("`self` is not a `HybridBaseSpec`! Did you accidentally inherit from `HybridBaseSpec_base` instead of `HybridBaseSpec`?")
}
#endif
if let cxxWrapper = self.cxxWrapper {
return cxxWrapper
} else {
let cxxWrapper = HybridBaseSpec_cxx(self as! HybridBaseSpec)
self.cxxWrapper = cxxWrapper
return cxxWrapper
}
}
public var hybridContext = margelo.nitro.HybridContext()
public var memorySize: Int { return getSizeOf(self) }
}
Expand Down
Loading

0 comments on commit 50e0de3

Please sign in to comment.