From ed4bedb226150c055a4ce0cee994faea41f846c2 Mon Sep 17 00:00:00 2001 From: William Taylor Date: Tue, 20 Oct 2026 10:13:50 +1100 Subject: [PATCH] BridgeJS: Escape reserved words --- .../Sources/BridgeJSCore/ExportSwift.swift | 18 +- .../Sources/BridgeJSLink/BridgeJSLink.swift | 31 +- .../MacroSwift/ReservedWordParameters.swift | 1 + .../ReservedWordParameters.json | 81 ++++ .../ReservedWordParameters.swift | 13 + .../ReservedWordParameters.d.ts | 18 + .../ReservedWordParameters.js | 382 ++++++++++++++++++ .../BridgeJSRuntimeTests/ExportAPITests.swift | 3 + .../Generated/BridgeJS.swift | 15 + .../Generated/JavaScript/BridgeJS.json | 55 +++ Tests/prelude.mjs | 1 + 11 files changed, 605 insertions(+), 13 deletions(-) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ReservedWordParameters.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ReservedWordParameters.json create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ReservedWordParameters.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ReservedWordParameters.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ReservedWordParameters.js diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 7d402aa9c..cf0907fa9 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -216,7 +216,7 @@ public class ExportSwift { let liftingInfo = try param.type.liftParameterInfo() let argumentsToLift: [String] if liftingInfo.parameters.count == 1 { - argumentsToLift = [param.name] + argumentsToLift = [param.escapedSwiftIdentifier] } else { argumentsToLift = liftingInfo.parameters.map { (name, _) in param.name + name.capitalizedFirstLetter } } @@ -227,7 +227,9 @@ public class ExportSwift { switch param.type { case .closure(let signature, _): typeNameForIntrinsic = param.type.swiftType - liftingExpr = ExprSyntax("_BJS_Closure_\(raw: signature.mangleName).bridgeJSLift(\(raw: param.name))") + liftingExpr = ExprSyntax( + "_BJS_Closure_\(raw: signature.mangleName).bridgeJSLift(\(raw: param.escapedSwiftIdentifier))" + ) case .swiftStruct(let structName): typeNameForIntrinsic = structName liftingExpr = ExprSyntax("\(raw: structName).bridgeJSLiftParameter()") @@ -252,7 +254,7 @@ public class ExportSwift { ) } - parameterBindings.insert("let \(raw: param.name) = \(liftingExpr)", at: 0) + parameterBindings.insert("let \(raw: param.escapedSwiftIdentifier) = \(liftingExpr)", at: 0) for (name, type) in zip(argumentsToLift, liftingInfo.parameters.map { $0.type }) { abiParameterSignatures.append((name, type)) } @@ -268,14 +270,14 @@ public class ExportSwift { } private func removeFirstLiftedParameter() -> String { - return parameters.removeFirst().name + return parameters.removeFirst().escapedSwiftIdentifier } private func renderCallStatement(callee: ExprSyntax, returnType: BridgeType) -> CodeBlockItemSyntax { let labeledParams = parameters.map { param in LabeledExprSyntax( label: param.label, - expression: DeclReferenceExprSyntax(baseName: .identifier(param.name)) + expression: DeclReferenceExprSyntax(baseName: .identifier(param.escapedSwiftIdentifier)) ) } var callExpr: ExprSyntax = @@ -1593,6 +1595,12 @@ struct ProtocolCodegen { } } +extension Parameter { + fileprivate var escapedSwiftIdentifier: String { + name.backtickIfNeeded() + } +} + extension WasmCoreType { var swiftType: String { switch self { diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 8e7c4939c..ee9531d31 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -1758,7 +1758,7 @@ public struct BridgeJSLink { loweringFragment.parameters.count == 1, "Lowering fragment should have exactly one parameter to lower" ) - let paramName = scope.variable(param.name) + let paramName = scope.variable(param.escapedJSIdentifier) let loweredValues = try loweringFragment.printCode([paramName], context) parameterForwardings.append(contentsOf: loweredValues) } @@ -1950,7 +1950,7 @@ public struct BridgeJSLink { } let parameterSignatures = parameters.map { param in let optional = param.hasDefault ? "?" : "" - return "\(param.name)\(optional): \(resolveTypeScriptType(param.type))" + return "\(param.escapedJSIdentifier)\(optional): \(resolveTypeScriptType(param.type))" } return "(\(parameterSignatures.joined(separator: ", "))): \(returnTypeWithEffect)" } @@ -2669,7 +2669,7 @@ extension BridgeJSLink { if let constructor = klass.constructor { let paramSignatures = constructor.parameters.map { param in let optional = param.hasDefault ? "?" : "" - return "\(param.name)\(optional): \(resolveTypeScriptType(param.type))" + return "\(param.escapedJSIdentifier)\(optional): \(resolveTypeScriptType(param.type))" } printer.write( lines: renderJSDoc(documentation: constructor.documentation, parameters: constructor.parameters) @@ -4321,9 +4321,9 @@ enum DefaultValueUtils { return parameters.map { param in if let defaultValue = param.defaultValue { let defaultJs = format(defaultValue, as: .javascript, resolveTypeName: resolveTypeName) - return "\(param.name) = \(defaultJs)" + return "\(param.escapedJSIdentifier) = \(defaultJs)" } - return param.name + return param.escapedJSIdentifier }.joined(separator: ", ") } } @@ -4348,11 +4348,11 @@ extension BridgeJSLink { } switch (docText, defaultValue) { case let (.some(text), .some(value)): - tagLines.append("@param \(parameter.name) \(text) (default: \(value))") + tagLines.append("@param \(parameter.escapedJSIdentifier) \(text) (default: \(value))") case let (.some(text), .none): - tagLines.append("@param \(parameter.name) \(text)") + tagLines.append("@param \(parameter.escapedJSIdentifier) \(text)") case let (.none, .some(value)): - tagLines.append("@param \(parameter.name) - Optional parameter (default: \(value))") + tagLines.append("@param \(parameter.escapedJSIdentifier) - Optional parameter (default: \(value))") case (.none, .none): continue } @@ -4477,6 +4477,21 @@ struct BridgeJSLinkError: Error { let message: String } +extension Parameter { + // From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words + private static let javaScriptReservedWords: Set = [ + "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do", "else", + "export", "extends", "false", "finally", "for", "function", "if", "import", "in", "instanceof", "new", + "null", "return", "super", "switch", "this", "throw", "true", "try", "typeof", "var", "void", "while", + "with", "let", "static", "yield", "await", "enum", "implements", "interface", "package", "private", + "protected", "public", "arguments", "eval", + ] + + fileprivate var escapedJSIdentifier: String { + Self.javaScriptReservedWords.contains(name) ? "_\(name)" : name + } +} + extension ClosureSignature { fileprivate func renderTSFunctionType(renderType: (BridgeType) -> String) -> String { let renderedParameters = parameters.enumerated().map { index, parameter in diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ReservedWordParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ReservedWordParameters.swift new file mode 100644 index 000000000..2c4c062cb --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ReservedWordParameters.swift @@ -0,0 +1 @@ +@JS func check(extension: Int, break: Double, function: Bool, in: String) {} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ReservedWordParameters.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ReservedWordParameters.json new file mode 100644 index 000000000..c27a4eb46 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ReservedWordParameters.json @@ -0,0 +1,81 @@ +{ + "exported" : { + "aliases" : [ + + ], + "classes" : [ + + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + { + "abiName" : "bjs_check", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "check", + "parameters" : [ + { + "label" : "extension", + "name" : "extension", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "label" : "break", + "name" : "break", + "type" : { + "double" : { + + } + } + }, + { + "label" : "function", + "name" : "function", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "in", + "name" : "in", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "void" : { + + } + } + } + ], + "protocols" : [ + + ], + "structs" : [ + + ] + }, + "moduleName" : "TestModule", + "usedExternalModules" : [ + + ] +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ReservedWordParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ReservedWordParameters.swift new file mode 100644 index 000000000..b09e1a76d --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ReservedWordParameters.swift @@ -0,0 +1,13 @@ +@_expose(wasm, "bjs_check") +@_cdecl("bjs_check") +public func _bjs_check(_ `extension`: Int32, _ `break`: Float64, _ function: Int32, _ inBytes: Int32, _ inLength: Int32) -> Void { + #if arch(wasm32) + let `in` = String.bridgeJSLiftParameter(inBytes, inLength) + let function = Bool.bridgeJSLiftParameter(function) + let `break` = Double.bridgeJSLiftParameter(`break`) + let `extension` = Int.bridgeJSLiftParameter(`extension`) + check(extension: `extension`, break: `break`, function: function, in: `in`) + #else + fatalError("Only available on WebAssembly") + #endif +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ReservedWordParameters.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ReservedWordParameters.d.ts new file mode 100644 index 000000000..027a6b9db --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ReservedWordParameters.d.ts @@ -0,0 +1,18 @@ +// 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 = { + check(extension: number, _break: number, _function: boolean, _in: string): void; +} +export type Imports = { +} +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/ReservedWordParameters.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ReservedWordParameters.js new file mode 100644 index 000000000..0abe5fdbf --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ReservedWordParameters.js @@ -0,0 +1,382 @@ +// 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; + let decodeUTF8; + const immortalStrings = new Map(); + const textDecoder = new TextDecoder("utf-8", { ignoreBOM: true }); + 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 i64Stack = []; + let f32Stack = []; + let f64Stack = []; + let ptrStack = []; + let taStack = []; + const enumHelpers = {}; + const structHelpers = {}; + + let _exports = null; + let bjs = null; + + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + bjs = {}; + importObject["bjs"] = bjs; + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); + } + 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 >>> 0); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); + 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(word0, word1, word2) { + const value = decodeString(word0, word1, word2); + 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_push_i64"] = function(v) { + i64Stack.push(v); + } + bjs["swift_js_pop_i64"] = function() { + return i64Stack.pop(); + } + const taCtors = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array]; + bjs["swift_js_push_typed_array"] = function(kind, ptr, count) { + const Ctor = taCtors[kind]; + const byteLen = count * Ctor.BYTES_PER_ELEMENT; + const copy = memory.buffer.slice(ptr, ptr + byteLen); + taStack.push(Array.from(new Ctor(copy))); + } + bjs["bjs_core_register_type_handles"] = function() {}; + const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); + bjs["swift_js_make_promise"] = function() { + let resolve, reject; + const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); + promise[__bjs_promiseSettlers] = { resolve, reject }; + return swift.memory.retain(promise); + } + 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, word0, word1, word2) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = decodeString(word0, word1, word2); + } + } + 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) {} + }, + setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); + instance = i; + memory = instance.exports.memory; + + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : (word1 >>> ((byteIndex - 4) * 8)) & 255; + // Valid small Swift strings need at most eight UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, count) => { + if (count > 8) { + throw new Error(`Unsupported Swift inline String length: ${count} UTF-8 bytes (maximum 8). The Swift String layout may have changed; update JavaScriptKit and regenerate BridgeJS glue.`); + } + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0; + while (byteIndex < count) { + const b0 = byteAt(word0, word1, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, byteIndex++), + b2 = byteAt(word0, word1, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, byteIndex++), + b2 = byteAt(word0, word1, byteIndex++), + b3 = byteAt(word0, word1, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + default: + throw new Error(`Unsupported Swift inline String length: ${count} UTF-8 bytes (maximum 8). The Swift String layout may have changed; update JavaScriptKit and regenerate BridgeJS glue.`); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); + + 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 = { + check: function bjs_check(extension, _break, _function, _in) { + const _inBytes = textEncoder.encode(_in); + const _inId = swift.memory.retain(_inBytes); + instance.exports.bjs_check(extension, _break, _function, _inId, _inBytes.length); + }, + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index c231d0ce9..9e5892a87 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -22,6 +22,9 @@ func runJsWorks() -> Void @JS func roundTripString(v: String) -> String { return v } +@JS func describeReservedWordParameters(extension: Int, break: Double, function: Bool, in: String) -> String { + return "\(`extension`):\(`break`):\(function):\(`in`)" +} @JS func roundTripSwiftHeapObject(v: Greeter) -> Greeter { return v } diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index b89bdb510..884ecb8e1 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -10324,6 +10324,21 @@ public func _bjs_roundTripString(_ vBytes: Int32, _ vLength: Int32) -> Void { #endif } +@_expose(wasm, "bjs_describeReservedWordParameters") +@_cdecl("bjs_describeReservedWordParameters") +public func _bjs_describeReservedWordParameters(_ `extension`: Int32, _ `break`: Float64, _ function: Int32, _ inBytes: Int32, _ inLength: Int32) -> Void { + #if arch(wasm32) + let `in` = String.bridgeJSLiftParameter(inBytes, inLength) + let function = Bool.bridgeJSLiftParameter(function) + let `break` = Double.bridgeJSLiftParameter(`break`) + let `extension` = Int.bridgeJSLiftParameter(`extension`) + let ret = describeReservedWordParameters(extension: `extension`, break: `break`, function: function, in: `in`) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_roundTripSwiftHeapObject") @_cdecl("bjs_roundTripSwiftHeapObject") public func _bjs_roundTripSwiftHeapObject(_ v: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index 9d5da0a7f..8598361b6 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -15214,6 +15214,61 @@ } } }, + { + "abiName" : "bjs_describeReservedWordParameters", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "describeReservedWordParameters", + "parameters" : [ + { + "label" : "extension", + "name" : "extension", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "label" : "break", + "name" : "break", + "type" : { + "double" : { + + } + } + }, + { + "label" : "function", + "name" : "function", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "in", + "name" : "in", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, { "abiName" : "bjs_roundTripSwiftHeapObject", "effects" : { diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index f54e3a149..15631bcbe 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -305,6 +305,7 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { ]) { assert.equal(exports.roundTripString(v), v); } + assert.equal(exports.describeReservedWordParameters(1, 2.5, true, "in"), "1:2.5:true:in"); const dict = { a: 1, b: 2 }; assert.deepEqual(exports.roundTripDictionaryExport(dict), dict); const optDict = { hello: "world" };