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
26 changes: 22 additions & 4 deletions Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ struct IntrinsicJSFragment: Sendable {
}

let innerFragment =
if wrappedType.optionalConvention == .stackABI {
if wrappedType.optionalParameterUsesStackABI {
try stackLiftFragment(elementType: wrappedType)
} else {
try liftParameter(type: wrappedType, context: bridgeContext)
Expand All @@ -686,7 +686,7 @@ struct IntrinsicJSFragment: Sendable {
kind: JSOptionalKind,
innerFragment: IntrinsicJSFragment
) -> IntrinsicJSFragment {
let isStackConvention = wrappedType.optionalConvention == .stackABI
let isStackConvention = wrappedType.optionalParameterUsesStackABI
let absenceLiteral = kind.absenceLiteral

let outerParams: [String]
Expand Down Expand Up @@ -762,7 +762,7 @@ struct IntrinsicJSFragment: Sendable {
}

let innerFragment =
if wrappedType.optionalConvention == .stackABI {
if wrappedType.optionalParameterUsesStackABI {
try stackLowerFragment(elementType: wrappedType)
} else {
try lowerParameter(type: wrappedType)
Expand All @@ -779,7 +779,7 @@ struct IntrinsicJSFragment: Sendable {
kind: JSOptionalKind,
innerFragment: IntrinsicJSFragment
) throws -> IntrinsicJSFragment {
let isStackConvention = wrappedType.optionalConvention == .stackABI
let isStackConvention = wrappedType.optionalParameterUsesStackABI

return IntrinsicJSFragment(
parameters: ["value"],
Expand Down Expand Up @@ -2696,6 +2696,24 @@ private extension BridgeType {
}
}

/// Whether an optional of this type pushes its payload onto the bridge stack
/// when passed as a *parameter*.
///
/// This usually matches `optionalConvention == .stackABI`, but `jsObject`
/// optionals are the exception: their return values travel through the stack
/// while their parameters use the direct `(isSome, objId)` ABI, matching plain
/// `Optional<JSObject>` and exported `@JS class` parameters.
var optionalParameterUsesStackABI: Bool {
switch self {
case .jsObject:
return false
case .nullable(let wrapped, _):
return wrapped.optionalParameterUsesStackABI
default:
return optionalConvention == .stackABI
}
}

var nilSentinel: NilSentinel {
switch self {
case .swiftProtocol:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ class OptionalPropertyHolder {

@JS func testOptionalPropertyRoundtrip(_ holder: OptionalPropertyHolder?) -> OptionalPropertyHolder?

// Exported functions taking an optional jsObject use the direct (isSome, objId)
// parameter ABI; the return value travels through the stack ABI.
@JS func roundTripExportedOptionalJSObject(value: JSObject?) -> JSObject?

// Exported function taking/returning an optional imported @JSClass (issue #751).
@JS func roundTripExportedOptionalJSClass(value: WithOptionalJSClass?) -> WithOptionalJSClass?

@JS
func roundTripString(name: String?) -> String? {
return name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,76 @@
}
}
},
{
"abiName" : "bjs_roundTripExportedOptionalJSObject",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"name" : "roundTripExportedOptionalJSObject",
"parameters" : [
{
"label" : "value",
"name" : "value",
"type" : {
"nullable" : {
"_0" : {
"jsObject" : {

}
},
"_1" : "null"
}
}
}
],
"returnType" : {
"nullable" : {
"_0" : {
"jsObject" : {

}
},
"_1" : "null"
}
}
},
{
"abiName" : "bjs_roundTripExportedOptionalJSClass",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"name" : "roundTripExportedOptionalJSClass",
"parameters" : [
{
"label" : "value",
"name" : "value",
"type" : {
"nullable" : {
"_0" : {
"jsObject" : {
"_0" : "WithOptionalJSClass"
}
},
"_1" : "null"
}
}
}
],
"returnType" : {
"nullable" : {
"_0" : {
"jsObject" : {
"_0" : "WithOptionalJSClass"
}
},
"_1" : "null"
}
}
},
{
"abiName" : "bjs_roundTripString",
"effects" : {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,28 @@ public func _bjs_testOptionalPropertyRoundtrip(_ holderIsSome: Int32, _ holderVa
#endif
}

@_expose(wasm, "bjs_roundTripExportedOptionalJSObject")
@_cdecl("bjs_roundTripExportedOptionalJSObject")
public func _bjs_roundTripExportedOptionalJSObject(_ valueIsSome: Int32, _ valueValue: Int32) -> Void {
#if arch(wasm32)
let ret = roundTripExportedOptionalJSObject(value: Optional<JSObject>.bridgeJSLiftParameter(valueIsSome, valueValue))
return ret.bridgeJSLowerReturn()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_roundTripExportedOptionalJSClass")
@_cdecl("bjs_roundTripExportedOptionalJSClass")
public func _bjs_roundTripExportedOptionalJSClass(_ valueIsSome: Int32, _ valueValue: Int32) -> Void {
#if arch(wasm32)
let ret = roundTripExportedOptionalJSClass(value: Optional<WithOptionalJSClass>.bridgeJSLiftParameter(valueIsSome, valueValue))
return ret.bridgeJSLowerReturn()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_roundTripString")
@_cdecl("bjs_roundTripString")
public func _bjs_roundTripString(_ nameIsSome: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export type Exports = {
}
roundTripOptionalClass(value: Greeter | null): Greeter | null;
testOptionalPropertyRoundtrip(holder: OptionalPropertyHolder | null): OptionalPropertyHolder | null;
roundTripExportedOptionalJSObject(value: any | null): any | null;
roundTripExportedOptionalJSClass(value: WithOptionalJSClass | null): WithOptionalJSClass | null;
roundTripString(name: string | null): string | null;
roundTripInt(value: number | null): number | null;
roundTripInt8(value: number | null): number | null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,18 +387,9 @@ export async function createInstantiator(options, swift) {
setException(error);
}
}
TestModule["bjs_WithOptionalJSClass_childOrNull_set"] = function bjs_WithOptionalJSClass_childOrNull_set(self, newValue) {
TestModule["bjs_WithOptionalJSClass_childOrNull_set"] = function bjs_WithOptionalJSClass_childOrNull_set(self, newValueIsSome, newValueObjectId) {
try {
let optResult;
if (newValue) {
const objId = i32Stack.pop();
const obj = swift.memory.getObject(objId);
swift.memory.release(objId);
optResult = obj;
} else {
optResult = null;
}
swift.memory.getObject(self).childOrNull = optResult;
swift.memory.getObject(self).childOrNull = newValueIsSome ? swift.memory.getObject(newValueObjectId) : null;
} catch (error) {
setException(error);
}
Expand Down Expand Up @@ -489,22 +480,13 @@ export async function createInstantiator(options, swift) {
setException(error);
}
}
TestModule["bjs_WithOptionalJSClass_roundTripChildOrNull"] = function bjs_WithOptionalJSClass_roundTripChildOrNull(self, value) {
TestModule["bjs_WithOptionalJSClass_roundTripChildOrNull"] = function bjs_WithOptionalJSClass_roundTripChildOrNull(self, valueIsSome, valueObjectId) {
try {
let optResult;
if (value) {
const objId = i32Stack.pop();
const obj = swift.memory.getObject(objId);
swift.memory.release(objId);
optResult = obj;
} else {
optResult = null;
}
let ret = swift.memory.getObject(self).roundTripChildOrNull(optResult);
let ret = swift.memory.getObject(self).roundTripChildOrNull(valueIsSome ? swift.memory.getObject(valueObjectId) : null);
const isSome = ret != null;
if (isSome) {
const objId1 = swift.memory.retain(ret);
i32Stack.push(objId1);
const objId = swift.memory.retain(ret);
i32Stack.push(objId);
}
i32Stack.push(isSome ? 1 : 0);
} catch (error) {
Expand Down Expand Up @@ -725,6 +707,48 @@ export async function createInstantiator(options, swift) {
const optResult = pointer === null ? null : OptionalPropertyHolder.__construct(pointer);
return optResult;
},
roundTripExportedOptionalJSObject: function bjs_roundTripExportedOptionalJSObject(value) {
const isSome = value != null;
let result;
if (isSome) {
result = swift.memory.retain(value);
} else {
result = 0;
}
instance.exports.bjs_roundTripExportedOptionalJSObject(+isSome, result);
const isSome1 = i32Stack.pop();
let optResult;
if (isSome1) {
const objId = i32Stack.pop();
const obj = swift.memory.getObject(objId);
swift.memory.release(objId);
optResult = obj;
} else {
optResult = null;
}
return optResult;
},
roundTripExportedOptionalJSClass: function bjs_roundTripExportedOptionalJSClass(value) {
const isSome = value != null;
let result;
if (isSome) {
result = swift.memory.retain(value);
} else {
result = 0;
}
instance.exports.bjs_roundTripExportedOptionalJSClass(+isSome, result);
const isSome1 = i32Stack.pop();
let optResult;
if (isSome1) {
const objId = i32Stack.pop();
const obj = swift.memory.getObject(objId);
swift.memory.release(objId);
optResult = obj;
} else {
optResult = null;
}
return optResult;
},
roundTripString: function bjs_roundTripString(name) {
const isSome = name != null;
let result, result1;
Expand Down
23 changes: 23 additions & 0 deletions Sources/JavaScriptKit/BridgeJSIntrinsics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,29 @@ extension _BridgedAsOptional where Wrapped == JSObject {
}
}

extension _BridgedAsOptional where Wrapped: _JSBridgedClass {
// `@JSClass` wrappers (`_JSBridgedClass`) bridge an underlying `JSObject`, so an
// optional wrapper mirrors `Optional<JSObject>`: parameters use the direct
// (`isSome`, object id) ABI while returns travel through the bridge stack.
//
// Stack push/pop is provided by the generic `Wrapped: _BridgedSwiftStackType`
// extension; only the direct parameter lift and the export return lowering need
// dedicated implementations here.
@_spi(BridgeJS) public static func bridgeJSLiftParameter(_ isSome: Int32, _ objectId: Int32) -> Self {
Self(
optional: Optional<Wrapped>._bridgeJSLiftParameter(
isSome,
objectId,
liftWrapped: Wrapped.bridgeJSLiftParameter
)
)
}

@_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Void {
Wrapped.bridgeJSStackPushAsOptional(asOptional)
}
}

extension _BridgedAsOptional where Wrapped: _BridgedSwiftProtocolWrapper {
@_spi(BridgeJS) public static func bridgeJSLiftParameter(_ isSome: Int32, _ objectId: Int32) -> Self {
Self(
Expand Down
4 changes: 4 additions & 0 deletions Tests/BridgeJSRuntimeTests/ExportAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ func runJsWorks() -> Void
return try Foo(value)
}

@JS func roundTripOptionalImportedClass(v: Foo?) -> Foo? {
return v
}

struct TestError: Error {
let message: String
}
Expand Down
32 changes: 32 additions & 0 deletions Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6940,6 +6940,17 @@ public func _bjs_makeImportedFoo(_ valueBytes: Int32, _ valueLength: Int32) -> I
#endif
}

@_expose(wasm, "bjs_roundTripOptionalImportedClass")
@_cdecl("bjs_roundTripOptionalImportedClass")
public func _bjs_roundTripOptionalImportedClass(_ vIsSome: Int32, _ vValue: Int32) -> Void {
#if arch(wasm32)
let ret = roundTripOptionalImportedClass(v: Optional<Foo>.bridgeJSLiftParameter(vIsSome, vValue))
return ret.bridgeJSLowerReturn()
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_throwsSwiftError")
@_cdecl("bjs_throwsSwiftError")
public func _bjs_throwsSwiftError(_ shouldThrow: Int32) -> Void {
Expand Down Expand Up @@ -14076,6 +14087,18 @@ fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalStringToStringDic
return bjs_OptionalSupportImports_jsRoundTripOptionalStringToStringDictionaryUndefined_static_extern(v)
}

#if arch(wasm32)
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_OptionalSupportImports_jsRoundTripOptionalJSObjectNull_static")
fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalJSObjectNull_static_extern(_ valueIsSome: Int32, _ valueValue: Int32) -> Void
#else
fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalJSObjectNull_static_extern(_ valueIsSome: Int32, _ valueValue: Int32) -> Void {
fatalError("Only available on WebAssembly")
}
#endif
@inline(never) fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalJSObjectNull_static(_ valueIsSome: Int32, _ valueValue: Int32) -> Void {
return bjs_OptionalSupportImports_jsRoundTripOptionalJSObjectNull_static_extern(valueIsSome, valueValue)
}

#if arch(wasm32)
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_OptionalSupportImports_runJsOptionalSupportTests_static")
fileprivate func bjs_OptionalSupportImports_runJsOptionalSupportTests_static_extern() -> Void
Expand Down Expand Up @@ -14162,6 +14185,15 @@ func _$OptionalSupportImports_jsRoundTripOptionalStringToStringDictionaryUndefin
return JSUndefinedOr<[String: String]>.bridgeJSLiftReturn()
}

func _$OptionalSupportImports_jsRoundTripOptionalJSObjectNull(_ value: Optional<JSObject>) throws(JSException) -> Optional<JSObject> {
let (valueIsSome, valueValue) = value.bridgeJSLowerParameter()
bjs_OptionalSupportImports_jsRoundTripOptionalJSObjectNull_static(valueIsSome, valueValue)
if let error = _swift_js_take_exception() {
throw error
}
return Optional<JSObject>.bridgeJSLiftReturn()
}

func _$OptionalSupportImports_runJsOptionalSupportTests() throws(JSException) -> Void {
bjs_OptionalSupportImports_runJsOptionalSupportTests_static()
if let error = _swift_js_take_exception() {
Expand Down
Loading
Loading