diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index dbe4a1312..41f24743a 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -734,7 +734,7 @@ struct StackCodegen { func liftExpression(for type: BridgeType) -> ExprSyntax { switch type { case .string, .int, .uint, .bool, .float, .double, - .jsObject(nil), .jsValue, .swiftStruct, .swiftHeapObject, .unsafePointer, + .jsObject(nil), .jsString, .jsValue, .swiftStruct, .swiftHeapObject, .unsafePointer, .swiftProtocol, .caseEnum, .associatedValueEnum, .rawValueEnum, .array, .dictionary: return "\(raw: type.swiftType).bridgeJSStackPop()" case .jsObject(let className?): @@ -751,7 +751,7 @@ struct StackCodegen { private func liftNullableExpression(wrappedType: BridgeType, kind: JSOptionalKind) -> ExprSyntax { let typeName = kind == .null ? "Optional" : "JSUndefinedOr" switch wrappedType { - case .string, .int, .uint, .bool, .float, .double, .jsObject(nil), .jsValue, + case .string, .int, .uint, .bool, .float, .double, .jsObject(nil), .jsString, .jsValue, .swiftStruct, .swiftHeapObject, .caseEnum, .associatedValueEnum, .rawValueEnum, .array, .dictionary: return "\(raw: typeName)<\(raw: wrappedType.swiftType)>.bridgeJSStackPop()" @@ -775,7 +775,7 @@ struct StackCodegen { ) -> [CodeBlockItemSyntax] { switch type { case .string, .int, .uint, .bool, .float, .double, .jsValue, - .jsObject(nil), .swiftHeapObject, .unsafePointer, .closure, + .jsObject(nil), .jsString, .swiftHeapObject, .unsafePointer, .closure, .caseEnum, .rawValueEnum, .associatedValueEnum, .swiftStruct, .nullable: return ["\(raw: accessor).bridgeJSStackPush()"] case .jsObject(_?): @@ -1385,6 +1385,7 @@ extension BridgeType { case .jsValue: return "JSValue" case .jsObject(nil): return "JSObject" case .jsObject(let name?): return name + case .jsString: return "JSString" case .swiftHeapObject(let name): return name case .unsafePointer(let ptr): return ptr.swiftType case .swiftProtocol(let name): return "Any\(name)" @@ -1431,6 +1432,7 @@ extension BridgeType { static let double = LiftingIntrinsicInfo(parameters: [("value", .f64)]) static let string = LiftingIntrinsicInfo(parameters: [("bytes", .i32), ("length", .i32)]) static let jsObject = LiftingIntrinsicInfo(parameters: [("value", .i32)]) + static let jsString = LiftingIntrinsicInfo(parameters: [("value", .i32)]) static let jsValue = LiftingIntrinsicInfo(parameters: [("kind", .i32), ("payload1", .i32), ("payload2", .f64)]) static let swiftHeapObject = LiftingIntrinsicInfo(parameters: [("value", .pointer)]) static let unsafePointer = LiftingIntrinsicInfo(parameters: [("pointer", .pointer)]) @@ -1449,11 +1451,14 @@ extension BridgeType { case .double: return .double case .string: return .string case .jsObject: return .jsObject + case .jsString: return .jsString case .jsValue: return .jsValue case .swiftHeapObject: return .swiftHeapObject 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 { @@ -1487,6 +1492,7 @@ extension BridgeType { static let double = LoweringIntrinsicInfo(returnType: .f64) static let string = LoweringIntrinsicInfo(returnType: nil) static let jsObject = LoweringIntrinsicInfo(returnType: .i32) + static let jsString = LoweringIntrinsicInfo(returnType: .i32) static let jsValue = LoweringIntrinsicInfo(returnType: nil) static let swiftHeapObject = LoweringIntrinsicInfo(returnType: .pointer) static let unsafePointer = LoweringIntrinsicInfo(returnType: .pointer) @@ -1507,11 +1513,14 @@ extension BridgeType { case .double: return .double case .string: return .string case .jsObject: return .jsObject + case .jsString: return .jsString case .jsValue: return .jsValue case .swiftHeapObject: return .swiftHeapObject 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): diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 1b7dce434..4504ea4e2 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -807,6 +807,7 @@ extension BridgeType { case .double: return .double case .string: return .string case .jsObject: return .jsObject + case .jsString: return LoweringParameterInfo(loweredParameters: [("value", .i32)], useBorrowing: true) case .jsValue: return .jsValue case .void: return .void case .closure: @@ -848,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)] @@ -881,6 +884,7 @@ extension BridgeType { case .double: return .double case .string: return .string case .jsObject: return .jsObject + case .jsString: return .jsObject case .jsValue: return .jsValue case .void: return .void case .closure: diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 93814123c..41a75bba5 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -3518,6 +3518,8 @@ extension BridgeType { return "boolean" case .jsObject(let name): return name ?? "any" + case .jsString: + return "string" case .jsValue: return "any" case .swiftHeapObject(let name): diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index bec5ce3e9..8d093a753 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -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( @@ -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 @@ -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: [], @@ -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) } @@ -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( @@ -1249,6 +1372,7 @@ struct IntrinsicJSFragment: Sendable { return .identity case .string: return .stringLowerParameter case .jsObject: return .jsObjectLowerParameter + case .jsString: return .jsObjectLowerParameter case .jsValue: return .jsValueLower case .swiftHeapObject: return .swiftHeapObjectLowerParameter case .swiftProtocol: return .jsObjectLowerParameter @@ -1299,6 +1423,7 @@ struct IntrinsicJSFragment: Sendable { return .identity case .string: return .stringLiftReturn case .jsObject: return .jsObjectLiftReturn + case .jsString: return .jsObjectLiftReturn case .jsValue: return .jsValueLift case .swiftHeapObject(let name): return .swiftHeapObjectLiftReturn(name) case .swiftProtocol: return .jsObjectLiftReturn @@ -1348,6 +1473,7 @@ struct IntrinsicJSFragment: Sendable { return .identity case .string: return .stringLiftParameter case .jsObject: return .jsObjectLiftParameter + case .jsString: return .jsObjectLiftParameter case .jsValue: return .jsValueLiftParameter case .swiftHeapObject(let name): return .swiftHeapObjectLiftParameter(name) @@ -1432,6 +1558,7 @@ struct IntrinsicJSFragment: Sendable { return .identity case .string: return .stringLowerReturn case .jsObject: return .jsObjectLowerReturn + case .jsString: return .jsObjectLowerReturn case .jsValue: return .jsValueLowerReturn(context: context) case .swiftHeapObject: return .swiftHeapObjectLowerReturn case .swiftProtocol: return .jsObjectLowerReturn @@ -1986,7 +2113,7 @@ struct IntrinsicJSFragment: Sendable { return [objVar] } ) - case .jsObject, .swiftProtocol: + case .jsObject, .jsString, .swiftProtocol: return IntrinsicJSFragment( parameters: [], printCode: { arguments, context in @@ -2108,7 +2235,7 @@ struct IntrinsicJSFragment: Sendable { return [] } ) - case .jsObject, .swiftProtocol: + case .jsObject, .jsString, .swiftProtocol: return IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in @@ -2587,6 +2714,8 @@ private extension BridgeType { return .sideChannelReturn(.storage) case .jsObject: return .sideChannelReturn(.retainedObject) + case .jsString: + return .sideChannelReturn(.retainedObject) case .jsValue: return .inlineFlag case .swiftHeapObject: @@ -2621,7 +2750,7 @@ private extension BridgeType { var nilSentinel: NilSentinel { switch self { - case .jsObject, .swiftProtocol: + case .jsObject, .jsString, .swiftProtocol: return .i32(0) case .swiftHeapObject: return .pointer @@ -2660,6 +2789,8 @@ private extension BridgeType { return [("bytes", .i32), ("length", .i32)] case .jsObject: return [("value", .i32)] + case .jsString: + return [("value", .i32)] case .jsValue: return [("kind", .i32), ("payload1", .i32), ("payload2", .f64)] case .swiftHeapObject: diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 967215f6c..ae28b6679 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -165,7 +165,7 @@ public enum JSOptionalKind: String, Codable, Equatable, Hashable, Sendable { } public enum BridgeType: Codable, Equatable, Hashable, Sendable { - case int, uint, float, double, string, bool, jsObject(String?), jsValue, swiftHeapObject(String), void + case int, uint, float, double, string, bool, jsObject(String?), jsString, jsValue, swiftHeapObject(String), void case unsafePointer(UnsafePointerType) indirect case nullable(BridgeType, JSOptionalKind) indirect case array(BridgeType) @@ -1074,6 +1074,8 @@ extension BridgeType { self = .void case "JSObject": self = .jsObject(nil) + case "JSString": + self = .jsString case "UnsafeRawPointer": self = .unsafePointer(.init(kind: .unsafeRawPointer)) case "UnsafeMutableRawPointer": @@ -1093,6 +1095,7 @@ extension BridgeType { case .float: return .f32 case .double: return .f64 case .string: return nil + case .jsString: return .i32 case .jsObject: return .i32 case .jsValue: return nil case .swiftHeapObject: @@ -1143,6 +1146,7 @@ extension BridgeType { case .float: return "Sf" case .double: return "Sd" case .string: return "SS" + case .jsString: return "8JSStringV" case .bool: return "Sb" case .void: return "y" case .jsObject(let name): diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSString.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSString.swift new file mode 100644 index 000000000..35a711375 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSString.swift @@ -0,0 +1,30 @@ +// Export: JSString parameter +@JS func checkJSString(a: JSString) {} + +// Export: JSString return +@JS func getJSString() -> JSString { fatalError() } + +// 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 parameter and return +@JS func checkUndefinedOrJSString(a: JSUndefinedOr) {} +@JS func getUndefinedOrJSString() -> JSUndefinedOr { fatalError() } +@JS func roundTripUndefinedOrJSString(_ value: JSUndefinedOr) -> JSUndefinedOr { 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 parameter and return +@JSFunction func jsCheckUndefinedOrJSString(_ a: JSUndefinedOr) throws(JSException) -> Void +@JSFunction func jsGetUndefinedOrJSString() throws(JSException) -> JSUndefinedOr diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSString.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSString.json new file mode 100644 index 000000000..89f280812 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSString.json @@ -0,0 +1,379 @@ +{ + "exported" : { + "classes" : [ + + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + { + "abiName" : "bjs_checkJSString", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "checkJSString", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "jsString" : { + + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getJSString", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getJSString", + "parameters" : [ + + ], + "returnType" : { + "jsString" : { + + } + } + }, + { + "abiName" : "bjs_roundTripJSString", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripJSString", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "jsString" : { + + } + } + } + ], + "returnType" : { + "jsString" : { + + } + } + }, + { + "abiName" : "bjs_checkOptionalJSString", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "checkOptionalJSString", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "nullable" : { + "_0" : { + "jsString" : { + + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getOptionalJSString", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getOptionalJSString", + "parameters" : [ + + ], + "returnType" : { + "nullable" : { + "_0" : { + "jsString" : { + + } + }, + "_1" : "null" + } + } + }, + { + "abiName" : "bjs_roundTripOptionalJSString", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripOptionalJSString", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "nullable" : { + "_0" : { + "jsString" : { + + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "jsString" : { + + } + }, + "_1" : "null" + } + } + }, + { + "abiName" : "bjs_checkUndefinedOrJSString", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "checkUndefinedOrJSString", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "nullable" : { + "_0" : { + "jsString" : { + + } + }, + "_1" : "undefined" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getUndefinedOrJSString", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "getUndefinedOrJSString", + "parameters" : [ + + ], + "returnType" : { + "nullable" : { + "_0" : { + "jsString" : { + + } + }, + "_1" : "undefined" + } + } + }, + { + "abiName" : "bjs_roundTripUndefinedOrJSString", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripUndefinedOrJSString", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "nullable" : { + "_0" : { + "jsString" : { + + } + }, + "_1" : "undefined" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "jsString" : { + + } + }, + "_1" : "undefined" + } + } + } + ], + "protocols" : [ + + ], + "structs" : [ + + ] + }, + "imported" : { + "children" : [ + { + "functions" : [ + { + "name" : "jsCheckJSString", + "parameters" : [ + { + "name" : "a", + "type" : { + "jsString" : { + + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "name" : "jsGetJSString", + "parameters" : [ + + ], + "returnType" : { + "jsString" : { + + } + } + }, + { + "name" : "jsCheckOptionalJSString", + "parameters" : [ + { + "name" : "a", + "type" : { + "nullable" : { + "_0" : { + "jsString" : { + + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "name" : "jsGetOptionalJSString", + "parameters" : [ + + ], + "returnType" : { + "nullable" : { + "_0" : { + "jsString" : { + + } + }, + "_1" : "null" + } + } + }, + { + "name" : "jsCheckUndefinedOrJSString", + "parameters" : [ + { + "name" : "a", + "type" : { + "nullable" : { + "_0" : { + "jsString" : { + + } + }, + "_1" : "undefined" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "name" : "jsGetUndefinedOrJSString", + "parameters" : [ + + ], + "returnType" : { + "nullable" : { + "_0" : { + "jsString" : { + + } + }, + "_1" : "undefined" + } + } + } + ], + "types" : [ + + ] + } + ] + }, + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSString.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSString.swift new file mode 100644 index 000000000..ac8e54347 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSString.swift @@ -0,0 +1,218 @@ +@_expose(wasm, "bjs_checkJSString") +@_cdecl("bjs_checkJSString") +public func _bjs_checkJSString(_ a: Int32) -> Void { + #if arch(wasm32) + checkJSString(a: JSString.bridgeJSLiftParameter(a)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getJSString") +@_cdecl("bjs_getJSString") +public func _bjs_getJSString() -> Int32 { + #if arch(wasm32) + let ret = getJSString() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripJSString") +@_cdecl("bjs_roundTripJSString") +public func _bjs_roundTripJSString(_ value: Int32) -> Int32 { + #if arch(wasm32) + let ret = roundTripJSString(_: JSString.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_checkOptionalJSString") +@_cdecl("bjs_checkOptionalJSString") +public func _bjs_checkOptionalJSString(_ a: Int32) -> Void { + #if arch(wasm32) + checkOptionalJSString(a: Optional.bridgeJSLiftParameter(a)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getOptionalJSString") +@_cdecl("bjs_getOptionalJSString") +public func _bjs_getOptionalJSString() -> Int32 { + #if arch(wasm32) + let ret = getOptionalJSString() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripOptionalJSString") +@_cdecl("bjs_roundTripOptionalJSString") +public func _bjs_roundTripOptionalJSString(_ value: Int32) -> Int32 { + #if arch(wasm32) + let ret = roundTripOptionalJSString(_: Optional.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_checkUndefinedOrJSString") +@_cdecl("bjs_checkUndefinedOrJSString") +public func _bjs_checkUndefinedOrJSString(_ a: Int32) -> Void { + #if arch(wasm32) + checkUndefinedOrJSString(a: JSUndefinedOr.bridgeJSLiftParameter(a)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getUndefinedOrJSString") +@_cdecl("bjs_getUndefinedOrJSString") +public func _bjs_getUndefinedOrJSString() -> Int32 { + #if arch(wasm32) + let ret = getUndefinedOrJSString() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripUndefinedOrJSString") +@_cdecl("bjs_roundTripUndefinedOrJSString") +public func _bjs_roundTripUndefinedOrJSString(_ value: Int32) -> Int32 { + #if arch(wasm32) + let ret = roundTripUndefinedOrJSString(_: JSUndefinedOr.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_jsCheckJSString") +fileprivate func bjs_jsCheckJSString_extern(_ a: Int32) -> Void +#else +fileprivate func bjs_jsCheckJSString_extern(_ a: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsCheckJSString(_ a: Int32) -> Void { + return bjs_jsCheckJSString_extern(a) +} + +func _$jsCheckJSString(_ a: JSString) throws(JSException) -> Void { + a.bridgeJSWithLoweredParameter { aValue in + bjs_jsCheckJSString(aValue) + } + if let error = _swift_js_take_exception() { + throw error + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_jsGetJSString") +fileprivate func bjs_jsGetJSString_extern() -> Int32 +#else +fileprivate func bjs_jsGetJSString_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsGetJSString() -> Int32 { + return bjs_jsGetJSString_extern() +} + +func _$jsGetJSString() throws(JSException) -> JSString { + let ret = bjs_jsGetJSString() + if let error = _swift_js_take_exception() { + throw error + } + return JSString.bridgeJSLiftReturn(ret) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_jsCheckOptionalJSString") +fileprivate func bjs_jsCheckOptionalJSString_extern(_ a: Int32) -> Void +#else +fileprivate func bjs_jsCheckOptionalJSString_extern(_ a: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsCheckOptionalJSString(_ a: Int32) -> Void { + return bjs_jsCheckOptionalJSString_extern(a) +} + +func _$jsCheckOptionalJSString(_ a: Optional) throws(JSException) -> Void { + a.bridgeJSWithLoweredParameter { aValue in + bjs_jsCheckOptionalJSString(aValue) + } + if let error = _swift_js_take_exception() { + throw error + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_jsGetOptionalJSString") +fileprivate func bjs_jsGetOptionalJSString_extern() -> Int32 +#else +fileprivate func bjs_jsGetOptionalJSString_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsGetOptionalJSString() -> Int32 { + return bjs_jsGetOptionalJSString_extern() +} + +func _$jsGetOptionalJSString() throws(JSException) -> Optional { + let ret = bjs_jsGetOptionalJSString() + if let error = _swift_js_take_exception() { + throw error + } + return Optional.bridgeJSLiftReturn(ret) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_jsCheckUndefinedOrJSString") +fileprivate func bjs_jsCheckUndefinedOrJSString_extern(_ a: Int32) -> Void +#else +fileprivate func bjs_jsCheckUndefinedOrJSString_extern(_ a: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsCheckUndefinedOrJSString(_ a: Int32) -> Void { + return bjs_jsCheckUndefinedOrJSString_extern(a) +} + +func _$jsCheckUndefinedOrJSString(_ a: JSUndefinedOr) throws(JSException) -> Void { + a.bridgeJSWithLoweredParameter { aValue in + bjs_jsCheckUndefinedOrJSString(aValue) + } + if let error = _swift_js_take_exception() { + throw error + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_jsGetUndefinedOrJSString") +fileprivate func bjs_jsGetUndefinedOrJSString_extern() -> Int32 +#else +fileprivate func bjs_jsGetUndefinedOrJSString_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsGetUndefinedOrJSString() -> Int32 { + return bjs_jsGetUndefinedOrJSString_extern() +} + +func _$jsGetUndefinedOrJSString() throws(JSException) -> JSUndefinedOr { + let ret = bjs_jsGetUndefinedOrJSString() + if let error = _swift_js_take_exception() { + throw error + } + return JSUndefinedOr.bridgeJSLiftReturn(ret) +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSString.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSString.d.ts new file mode 100644 index 000000000..558decb58 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSString.d.ts @@ -0,0 +1,32 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export type Exports = { + checkJSString(a: string): void; + getJSString(): string; + roundTripJSString(value: string): string; + checkOptionalJSString(a: string | null): void; + getOptionalJSString(): string | null; + roundTripOptionalJSString(value: string | null): string | null; + checkUndefinedOrJSString(a: string | undefined): void; + getUndefinedOrJSString(): string | undefined; + roundTripUndefinedOrJSString(value: string | undefined): string | undefined; +} +export type Imports = { + jsCheckJSString(a: string): void; + jsGetJSString(): string; + jsCheckOptionalJSString(a: string | null): void; + jsGetOptionalJSString(): string | null; + jsCheckUndefinedOrJSString(a: string | undefined): void; + jsGetUndefinedOrJSString(): string | undefined; +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSString.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSString.js new file mode 100644 index 000000000..479919f04 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSString.js @@ -0,0 +1,327 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + let decodeString; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + let tmpRetOptionalBool; + let tmpRetOptionalInt; + let tmpRetOptionalFloat; + let tmpRetOptionalDouble; + let tmpRetOptionalHeapObject; + let strStack = []; + let i32Stack = []; + let f32Stack = []; + let f64Stack = []; + let ptrStack = []; + const enumHelpers = {}; + const structHelpers = {}; + + let _exports = null; + let bjs = null; + + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + tmpRetString = decodeString(ptr, len); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + swift.memory.release(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + return swift.memory.retain(decodeString(ptr, len)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + bjs["swift_js_push_i32"] = function(v) { + i32Stack.push(v | 0); + } + bjs["swift_js_push_f32"] = function(v) { + f32Stack.push(Math.fround(v)); + } + bjs["swift_js_push_f64"] = function(v) { + f64Stack.push(v); + } + bjs["swift_js_push_string"] = function(ptr, len) { + const value = decodeString(ptr, len); + strStack.push(value); + } + bjs["swift_js_pop_i32"] = function() { + return i32Stack.pop(); + } + bjs["swift_js_pop_f32"] = function() { + return f32Stack.pop(); + } + bjs["swift_js_pop_f64"] = function() { + return f64Stack.pop(); + } + bjs["swift_js_push_pointer"] = function(pointer) { + ptrStack.push(pointer); + } + bjs["swift_js_pop_pointer"] = function() { + return ptrStack.pop(); + } + bjs["swift_js_return_optional_bool"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalBool = null; + } else { + tmpRetOptionalBool = value !== 0; + } + } + bjs["swift_js_return_optional_int"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalInt = null; + } else { + tmpRetOptionalInt = value | 0; + } + } + bjs["swift_js_return_optional_float"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalFloat = null; + } else { + tmpRetOptionalFloat = Math.fround(value); + } + } + bjs["swift_js_return_optional_double"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalDouble = null; + } else { + tmpRetOptionalDouble = value; + } + } + bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = decodeString(ptr, len); + } + } + bjs["swift_js_return_optional_object"] = function(isSome, objectId) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = swift.memory.getObject(objectId); + } + } + bjs["swift_js_return_optional_heap_object"] = function(isSome, pointer) { + if (isSome === 0) { + tmpRetOptionalHeapObject = null; + } else { + tmpRetOptionalHeapObject = pointer; + } + } + bjs["swift_js_get_optional_int_presence"] = function() { + return tmpRetOptionalInt != null ? 1 : 0; + } + bjs["swift_js_get_optional_int_value"] = function() { + const value = tmpRetOptionalInt; + tmpRetOptionalInt = undefined; + return value; + } + bjs["swift_js_get_optional_string"] = function() { + const str = tmpRetString; + tmpRetString = undefined; + if (str == null) { + return -1; + } else { + const bytes = textEncoder.encode(str); + tmpRetBytes = bytes; + return bytes.length; + } + } + bjs["swift_js_get_optional_float_presence"] = function() { + return tmpRetOptionalFloat != null ? 1 : 0; + } + bjs["swift_js_get_optional_float_value"] = function() { + const value = tmpRetOptionalFloat; + tmpRetOptionalFloat = undefined; + return value; + } + bjs["swift_js_get_optional_double_presence"] = function() { + return tmpRetOptionalDouble != null ? 1 : 0; + } + bjs["swift_js_get_optional_double_value"] = function() { + const value = tmpRetOptionalDouble; + tmpRetOptionalDouble = undefined; + return value; + } + bjs["swift_js_get_optional_heap_object_pointer"] = function() { + const pointer = tmpRetOptionalHeapObject; + tmpRetOptionalHeapObject = undefined; + return pointer || 0; + } + bjs["swift_js_closure_unregister"] = function(funcRef) {} + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; + TestModule["bjs_jsCheckJSString"] = function bjs_jsCheckJSString(a) { + try { + imports.jsCheckJSString(swift.memory.getObject(a)); + } catch (error) { + setException(error); + } + } + TestModule["bjs_jsGetJSString"] = function bjs_jsGetJSString() { + try { + let ret = imports.jsGetJSString(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_jsCheckOptionalJSString"] = function bjs_jsCheckOptionalJSString(a) { + try { + imports.jsCheckOptionalJSString(a === 0 ? null : swift.memory.getObject(a)); + } catch (error) { + setException(error); + } + } + TestModule["bjs_jsGetOptionalJSString"] = function bjs_jsGetOptionalJSString() { + try { + let ret = imports.jsGetOptionalJSString(); + const isSome = ret != null; + return isSome ? swift.memory.retain(ret) : 0; + } catch (error) { + setException(error); + } + } + TestModule["bjs_jsCheckUndefinedOrJSString"] = function bjs_jsCheckUndefinedOrJSString(a) { + try { + imports.jsCheckUndefinedOrJSString(a === 0 ? undefined : swift.memory.getObject(a)); + } catch (error) { + setException(error); + } + } + TestModule["bjs_jsGetUndefinedOrJSString"] = function bjs_jsGetUndefinedOrJSString() { + try { + let ret = imports.jsGetUndefinedOrJSString(); + const isSome = ret !== undefined; + return isSome ? swift.memory.retain(ret) : 0; + } catch (error) { + setException(error); + } + } + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + + decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + const exports = { + checkJSString: function bjs_checkJSString(a) { + instance.exports.bjs_checkJSString(swift.memory.retain(a)); + }, + getJSString: function bjs_getJSString() { + const ret = instance.exports.bjs_getJSString(); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; + }, + roundTripJSString: function bjs_roundTripJSString(value) { + const ret = instance.exports.bjs_roundTripJSString(swift.memory.retain(value)); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; + }, + checkOptionalJSString: function bjs_checkOptionalJSString(a) { + const isSome = a != null; + instance.exports.bjs_checkOptionalJSString(isSome ? swift.memory.retain(a) : 0); + }, + getOptionalJSString: function bjs_getOptionalJSString() { + const ret = instance.exports.bjs_getOptionalJSString(); + let optResult; + if (ret === 0) { + optResult = null; + } else { + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + optResult = ret1; + } + return optResult; + }, + roundTripOptionalJSString: function bjs_roundTripOptionalJSString(value) { + const isSome = value != null; + const ret = instance.exports.bjs_roundTripOptionalJSString(isSome ? swift.memory.retain(value) : 0); + let optResult; + if (ret === 0) { + optResult = null; + } else { + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + optResult = ret1; + } + return optResult; + }, + checkUndefinedOrJSString: function bjs_checkUndefinedOrJSString(a) { + const isSome = a !== undefined; + instance.exports.bjs_checkUndefinedOrJSString(isSome ? swift.memory.retain(a) : 0); + }, + getUndefinedOrJSString: function bjs_getUndefinedOrJSString() { + const ret = instance.exports.bjs_getUndefinedOrJSString(); + let optResult; + if (ret === 0) { + optResult = undefined; + } else { + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + optResult = ret1; + } + return optResult; + }, + roundTripUndefinedOrJSString: function bjs_roundTripUndefinedOrJSString(value) { + const isSome = value !== undefined; + const ret = instance.exports.bjs_roundTripUndefinedOrJSString(isSome ? swift.memory.retain(value) : 0); + let optResult; + if (ret === 0) { + optResult = undefined; + } else { + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + optResult = ret1; + } + return optResult; + }, + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file diff --git a/Sources/JavaScriptKit/BridgeJSIntrinsics.swift b/Sources/JavaScriptKit/BridgeJSIntrinsics.swift index f2b2b6c34..a71dd66f3 100644 --- a/Sources/JavaScriptKit/BridgeJSIntrinsics.swift +++ b/Sources/JavaScriptKit/BridgeJSIntrinsics.swift @@ -433,6 +433,39 @@ extension JSObject: _BridgedSwiftStackType { } } +extension JSString: _BridgedSwiftStackType { + public typealias StackLiftResult = JSString + + // MARK: ImportTS + + @_spi(BridgeJS) public func bridgeJSWithLoweredParameter(_ body: (Int32) -> T) -> T { + withExtendedLifetime(self.guts) { + body(Int32(bitPattern: self.asInternalJSRef())) + } + } + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ id: Int32) -> JSString { + JSString(jsRef: JavaScriptObjectRef(bitPattern: id)) + } + + // MARK: ExportSwift + + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ id: Int32) -> JSString { + JSString(jsRef: JavaScriptObjectRef(bitPattern: id)) + } + + @_spi(BridgeJS) public static func bridgeJSStackPop() -> JSString { + bridgeJSLiftParameter(_swift_js_pop_i32()) + } + + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Int32 { + withExtendedLifetime(self.guts) { _swift_js_retain(Int32(bitPattern: self.asInternalJSRef())) } + } + + @_spi(BridgeJS) public consuming func bridgeJSStackPush() { + _swift_js_push_i32(bridgeJSLowerReturn()) + } +} + extension JSValue: _BridgedSwiftStackType { public typealias StackLiftResult = JSValue @@ -1583,6 +1616,34 @@ extension _BridgedAsOptional where Wrapped == JSObject { } } +extension _BridgedAsOptional where Wrapped == JSString { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSWithLoweredParameter(_ body: (Int32) -> T) -> T { + switch asOptional { + case .none: + return body(0) + case .some(let value): + return value.bridgeJSWithLoweredParameter(body) + } + } + + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ value: Int32) -> Self { + Self(optional: value == 0 ? nil : JSString.bridgeJSLiftParameter(value)) + } + + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ value: Int32) -> Self { + Self(optional: value == 0 ? nil : JSString.bridgeJSLiftReturn(value)) + } + + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Int32 { + switch asOptional { + case .none: + return 0 + case .some(let value): + return value.bridgeJSLowerReturn() + } + } +} + extension _BridgedAsOptional where Wrapped: _BridgedSwiftProtocolWrapper { @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ isSome: Int32, _ objectId: Int32) -> Self { Self(