Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
changed lowering/lifting to sentinel method
  • Loading branch information
sliemeobn committed Mar 11, 2026
commit 7373485317001a9795a687c28d1720c5f780f3fb
4 changes: 4 additions & 0 deletions Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1457,6 +1457,8 @@ extension BridgeType {
case .unsafePointer: return .unsafePointer
case .swiftProtocol: return .jsObject
case .void: return .void
case .nullable(.jsString, _):
return .jsString
case .nullable(let wrappedType, _):
let wrappedInfo = try wrappedType.liftParameterInfo()
if wrappedInfo.parameters.isEmpty {
Expand Down Expand Up @@ -1517,6 +1519,8 @@ extension BridgeType {
case .unsafePointer: return .unsafePointer
case .swiftProtocol: return .jsObject
case .void: return .void
case .nullable(.jsString, _):
return .jsString
case .nullable: return .optional
case .caseEnum: return .caseEnum
case .rawValueEnum(_, let rawType):
Expand Down
2 changes: 2 additions & 0 deletions Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,8 @@ extension BridgeType {
}
case .namespaceEnum:
throw BridgeJSCoreError("Namespace enums cannot be used as parameters")
case .nullable(.jsString, _):
return LoweringParameterInfo(loweredParameters: [("value", .i32)], useBorrowing: true)
case .nullable(let wrappedType, _):
let wrappedInfo = try wrappedType.loweringParameterInfo(context: context)
var params = [("isSome", WasmCoreType.i32)]
Expand Down
123 changes: 123 additions & 0 deletions Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,15 @@ struct IntrinsicJSFragment: Sendable {
kind: JSOptionalKind,
context bridgeContext: BridgeContext = .importTS
) throws -> IntrinsicJSFragment {
if wrappedType == .jsString {
let innerFragment = try liftParameter(type: wrappedType, context: bridgeContext)
return sentinelOptionalLiftSingleValue(
wrappedType: wrappedType,
kind: kind,
innerFragment: { innerFragment }
)
}

if wrappedType.isSingleParamScalar {
let coerce = wrappedType.liftCoerce
return IntrinsicJSFragment(
Expand Down Expand Up @@ -716,6 +725,15 @@ struct IntrinsicJSFragment: Sendable {
wrappedType: BridgeType,
kind: JSOptionalKind
) throws -> IntrinsicJSFragment {
if wrappedType == .jsString {
let innerFragment = try lowerParameter(type: wrappedType)
return sentinelOptionalLowerSingleValue(
wrappedType: wrappedType,
kind: kind,
innerFragment: innerFragment
)
}

if wrappedType.isSingleParamScalar {
let wasmType = wrappedType.wasmParams[0].type
let coerce = wrappedType.lowerCoerce
Expand Down Expand Up @@ -807,6 +825,94 @@ struct IntrinsicJSFragment: Sendable {
)
}

private static func sentinelOptionalLiftSingleValue(
wrappedType: BridgeType,
kind: JSOptionalKind,
innerFragment: @escaping @Sendable () throws -> IntrinsicJSFragment
) -> IntrinsicJSFragment {
let sentinelLiteral = wrappedType.nilSentinel.jsLiteral
let absenceLiteral = kind.absenceLiteral
return IntrinsicJSFragment(
parameters: ["wrappedValue"],
printCode: { arguments, context in
let (scope, printer) = (context.scope, context.printer)
let wrappedValue = arguments[0]

let bufferPrinter = CodeFragmentPrinter()
let innerResults = try innerFragment().printCode(
[wrappedValue],
context.with(\.printer, bufferPrinter)
)
let innerExpr = innerResults.first ?? "undefined"

if bufferPrinter.lines.isEmpty {
return ["\(wrappedValue) === \(sentinelLiteral) ? \(absenceLiteral) : \(innerExpr)"]
}

let resultVar = scope.variable("optResult")
printer.write("let \(resultVar);")
printer.write("if (\(wrappedValue) === \(sentinelLiteral)) {")
printer.indent {
printer.write("\(resultVar) = \(absenceLiteral);")
}
printer.write("} else {")
printer.indent {
for line in bufferPrinter.lines {
printer.write(line)
}
printer.write("\(resultVar) = \(innerExpr);")
}
printer.write("}")
return [resultVar]
}
)
}

private static func sentinelOptionalLowerSingleValue(
wrappedType: BridgeType,
kind: JSOptionalKind,
innerFragment: IntrinsicJSFragment
) -> IntrinsicJSFragment {
let sentinelLiteral = wrappedType.nilSentinel.jsLiteral
return IntrinsicJSFragment(
parameters: ["value"],
printCode: { arguments, context in
let (scope, printer) = (context.scope, context.printer)
let value = arguments[0]
let isSomeVar = scope.variable("isSome")
let presenceExpr = kind.presenceCheck(value: value)
printer.write("const \(isSomeVar) = \(presenceExpr);")

let bufferPrinter = CodeFragmentPrinter()
let innerResults = try innerFragment.printCode(
[value],
context.with(\.printer, bufferPrinter)
)
let innerExpr = innerResults.first ?? sentinelLiteral

if bufferPrinter.lines.isEmpty {
return ["\(isSomeVar) ? \(innerExpr) : \(sentinelLiteral)"]
}

let resultVar = scope.variable("optResult")
printer.write("let \(resultVar);")
printer.write("if (\(isSomeVar)) {")
printer.indent {
for line in bufferPrinter.lines {
printer.write(line)
}
printer.write("\(resultVar) = \(innerExpr);")
}
printer.write("} else {")
printer.indent {
printer.write("\(resultVar) = \(sentinelLiteral);")
}
printer.write("}")
return [resultVar]
}
)
}

private static func optionalLiftReturnFromStorage(storage: String) -> IntrinsicJSFragment {
IntrinsicJSFragment(
parameters: [],
Expand Down Expand Up @@ -934,6 +1040,14 @@ struct IntrinsicJSFragment: Sendable {
wrappedType: BridgeType,
kind: JSOptionalKind
) -> IntrinsicJSFragment {
if wrappedType == .jsString {
return sentinelOptionalLiftSingleValue(
wrappedType: wrappedType,
kind: kind,
innerFragment: { try liftReturn(type: wrappedType) }
)
}

if let scalarKind = wrappedType.optionalScalarKind {
return optionalLiftReturnFromStorage(storage: scalarKind.storageName)
}
Expand Down Expand Up @@ -1068,6 +1182,15 @@ struct IntrinsicJSFragment: Sendable {
)
}

if wrappedType == .jsString {
let innerFragment = try lowerReturn(type: wrappedType, context: .exportSwift)
return sentinelOptionalLowerReturn(
wrappedType: wrappedType,
kind: kind,
innerFragment: innerFragment
)
}

if case .sideChannelReturn(let mode) = wrappedType.optionalConvention {
if mode == .none {
throw BridgeJSLinkError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1210,7 +1210,7 @@ extension BridgeType {
}

switch wrappedType {
case .string, .int, .float, .double, .jsObject, .jsString, .swiftProtocol:
case .string, .int, .float, .double, .jsObject, .swiftProtocol:
return true
case .rawValueEnum(_, let rawType):
switch rawType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@
// Export: JSString roundtrip
@JS func roundTripJSString(_ value: JSString) -> JSString { return value }

// Export: Optional JSString parameter and return
@JS func checkOptionalJSString(a: JSString?) {}
@JS func getOptionalJSString() -> JSString? { fatalError() }
@JS func roundTripOptionalJSString(_ value: JSString?) -> JSString? { return value }

// Export: JSUndefinedOr<JSString> parameter and return
@JS func checkUndefinedOrJSString(a: JSUndefinedOr<JSString>) {}
@JS func getUndefinedOrJSString() -> JSUndefinedOr<JSString> { fatalError() }
@JS func roundTripUndefinedOrJSString(_ value: JSUndefinedOr<JSString>) -> JSUndefinedOr<JSString> { return value }

// Import: JSString parameter and return
@JSFunction func jsCheckJSString(_ a: JSString) throws(JSException) -> Void
@JSFunction func jsGetJSString() throws(JSException) -> JSString

// Import: Optional JSString parameter and return
@JSFunction func jsCheckOptionalJSString(_ a: JSString?) throws(JSException) -> Void
@JSFunction func jsGetOptionalJSString() throws(JSException) -> JSString?

// Import: JSUndefinedOr<JSString> parameter and return
@JSFunction func jsCheckUndefinedOrJSString(_ a: JSUndefinedOr<JSString>) throws(JSException) -> Void
@JSFunction func jsGetUndefinedOrJSString() throws(JSException) -> JSUndefinedOr<JSString>
Loading
Loading