Skip to content
Open
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
37 changes: 28 additions & 9 deletions packages/nitrogen/src/syntax/swift/SwiftMethodHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SwiftBorrowedPointerType } from './types/SwiftBorrowedPointerType.js'
import { SwiftCxxBorrowedPointerType } from './types/SwiftCxxBorrowedPointerType.js'
import { SwiftCxxOutPointerType } from './types/SwiftCxxOutPointerType.js'
import { SwiftOutPointerType } from './types/SwiftOutPointerType.js'
import { SwiftResultConsumerType } from './types/SwiftResultConsumerType.js'
import { SwiftResultType } from './types/SwiftResultType.js'
import { SwiftRetainedPointerType } from './types/SwiftRetainedPointerType.js'
import { SwiftVoidType } from './types/SwiftVoidType.js'
Expand All @@ -22,6 +23,8 @@ export interface SwiftArgument {

/** Swift functions exported to C++ take result storage as their last parameter. */
export const SWIFT_RESULT_STORAGE = '__outResult'
/** Throwing Swift functions returning Swift values take a result consumer instead. */
const SWIFT_RESULT_CONSUMER = '__onResult'

/** A value passed between C++ and a Swift function exported to C++. */
export interface SwiftBridgeParameter {
Expand Down Expand Up @@ -88,8 +91,8 @@ export function prepareSwiftMethod(nativeMethod: Method, isThrowing: boolean, co
exportedReturnType = new SwiftRetainedPointerType(returnType)
} else if (isThrowing && !returnType.canImportFromCpp) {
// Swift cannot import Result<T> while a Swift-native T is incomplete.
// Return Result<void> alongside its value buffer; Core constructs Result<T>.
exportedReturnType = new SwiftOutPointerType(returnType)
// Return Result<void> and lend the successful value to C++ during the call.
exportedReturnType = new SwiftResultConsumerType(returnType)
} else {
exportedReturnType = returnType
}
Expand All @@ -104,16 +107,19 @@ export function prepareSwiftMethod(nativeMethod: Method, isThrowing: boolean, co
}
}

/** The Result returned by a Swift function; storage holds its value instead. */
/** The Result returned by a Swift function; indirect results use a separate parameter. */
export function getSwiftResultType(returnType: SwiftType, config: NitroConfig = NitroConfig.current): SwiftResultType {
if (returnType instanceof SwiftOutPointerType) {
if (returnType instanceof SwiftOutPointerType || returnType instanceof SwiftResultConsumerType) {
return new SwiftResultType(new SwiftVoidType(), config)
}
return new SwiftResultType(returnType, config)
}

/** The parameters of a Swift function exported to C++, including result storage. */
/** The parameters of a Swift function exported to C++, including indirect results. */
export function getSwiftBridgeParameters(parameters: SwiftBridgeParameter[], returnType: SwiftType): SwiftBridgeParameter[] {
if (returnType instanceof SwiftResultConsumerType) {
return [...parameters, { name: SWIFT_RESULT_CONSUMER, type: returnType }]
}
if (returnType instanceof SwiftOutPointerType) {
return [...parameters, { name: SWIFT_RESULT_STORAGE, type: returnType }]
}
Expand All @@ -130,13 +136,17 @@ interface CppToSwiftCallOptions {

/**
* C++ code that calls a Swift function and returns its value or throws its error.
* Pointer types pass the arguments and result storage around the call.
* Adapters borrow arguments and receive indirect results during the call.
*/
export function createCppToSwiftCall({ parameters, returnType, returnsVoid, getCppInvocation }: CppToSwiftCallOptions): string {
// Borrowed pointers shadow the values they point to.
const cppArguments = parameters.map((parameter) => parameter.name)
let cppSwiftInvocation: string
if (returnType instanceof SwiftOutPointerType) {
if (returnType instanceof SwiftResultConsumerType) {
const cppConsumerArguments = [...cppArguments, SWIFT_RESULT_CONSUMER]
const cppInvocation = getCppInvocation(cppConsumerArguments)
cppSwiftInvocation = returnType.receive({ consumer: SWIFT_RESULT_CONSUMER, invocation: cppInvocation })
} else if (returnType instanceof SwiftOutPointerType) {
const cppStorageArguments = [...cppArguments, SWIFT_RESULT_STORAGE]
cppSwiftInvocation = returnType.tryAllocate(SWIFT_RESULT_STORAGE, getCppInvocation(cppStorageArguments))
} else {
Expand Down Expand Up @@ -210,7 +220,7 @@ interface SwiftResultBodyOptions {

/**
* Swift statements that return the value of `invocation` to C++ as a Result,
* initializing the storage pointer when the value is returned through storage.
* supplying indirect results through a borrowed value or initialized storage.
*/
export function createSwiftResultBody({ invocation, returnType, resultType, returnsVoid }: SwiftResultBodyOptions): string {
if (returnsVoid) {
Expand All @@ -220,6 +230,14 @@ return ${resultType}.withValue()
`.trim()
}
const swiftValue = returnType.fromSwift('value')
if (returnType instanceof SwiftResultConsumerType) {
const consumeCode = returnType.consume({ consumer: SWIFT_RESULT_CONSUMER, value: swiftValue })
return `
let value = ${invocation}
${consumeCode}
return ${resultType}.withValue()
`.trim()
}
if (returnType instanceof SwiftOutPointerType) {
const initializeCode = returnType.initialize('swift', SWIFT_RESULT_STORAGE, swiftValue)
return `
Expand Down Expand Up @@ -318,5 +336,6 @@ export function getSwiftMethodImports(callable: SwiftMethodInfo): SourceImport<'
publicSwiftTypes.push(callable.exportedReturnType)
}
const swiftParameterTypes = callable.parameters.map((parameter) => parameter.type)
return [...getSwiftImports(publicSwiftTypes, 'public'), ...getSwiftImports([callable.returnType, ...swiftParameterTypes], 'internal')]
const swiftImplementationTypes = [callable.returnType, callable.exportedReturnType, ...swiftParameterTypes]
return [...getSwiftImports(publicSwiftTypes, 'public'), ...getSwiftImports(swiftImplementationTypes, 'internal')]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { Language } from '../../../getPlatformSpecs.js'
import { indent } from '../../../utils.js'
import type { SourceImport } from '../../SourceFile.js'
import { SwiftPointerType } from './SwiftPointerType.js'

interface ReceiveOptions {
/** The name of the consumer pointer. */
consumer: string
/** The Swift call that returns the status and lends its value to the consumer. */
invocation: string
}

interface ConsumeOptions {
/** The name of the consumer pointer. */
consumer: string
/** The bridge value to lend. */
value: string
}

/**
* Points to a C++ SwiftResultConsumer. Swift lends a successful return value to it,
* so the value does not cross the boundary in a boxed swift::Optional.
* TODO: Pass margelo.nitro.SwiftResultConsumer directly once our minimum Xcode includes
* https://github.com/swiftlang/swift/pull/93321, which fixes https://github.com/swiftlang/swift/issues/93318.
*/
export class SwiftResultConsumerType extends SwiftPointerType {
protected readonly isMutable = false

/** The value to lend; see `consume()`. */
fromSwift(value: string): string {
return this.pointee.fromSwift(value)
}
toSwift(value: string): string {
return this.pointee.toSwift(value)
}

/** C++ code that returns the Result of `invocation`, holding the value Swift lent to `consumer`. */
receive({ consumer, invocation }: ReceiveOptions): string {
const type = this.pointee.getCode('c++', { context: 'type-argument' })
const pointerType = this.getCode('c++', { context: 'value' })
return `
receiveSwiftResult<${type}>([&](${pointerType} ${consumer}) {
return ${indent(invocation, ' ')};
})
`.trim()
}

/** Swift code that lends `value` to `consumer` for the duration of the call. */
consume({ consumer, value }: ConsumeOptions): string {
return `
withUnsafePointer(to: ${value}) { pointer in
margelo.nitro.SwiftResultConsumer.consume(${consumer}, UnsafeRawPointer(pointer))
}
`.trim()
}

/** Swift calls the consumer through its C++ declaration. */
override getRequiredImports(language?: Language): SourceImport[] {
switch (language) {
case 'swift':
return [{ language: language, name: 'NitroModulesAppleCxx', access: 'internal' }]
default:
return []
}
}
}

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.

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
Loading