From fb711be451d1f2efc2c9ce81d00abf9b3b9938b3 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 7 Aug 2026 18:19:00 +0100 Subject: [PATCH] BridgeJS: normalize keyword-escaped names in generated code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `@JS func `default`() {}` produced `bjs_`default`` as the WebAssembly export symbol — an invalid `@_expose`/`@_cdecl` name — because the codegenerator read `TokenSyntax.text` (which keeps backticks for keyword-escaped identifiers) and never normalized it. The backticks leaked into ABI names, generated Swift identifiers, and JS/d.ts names. The fix splits name escaping into three context-aware helpers, grounded in swift-syntax's grammar (`isValidSwiftIdentifier(for:)`): - `backtickIfNeeded()` — declaration position (`var`/`let`/property/ `let`-binding). Keywords are not valid bare here; escapes all keywords including `self` (`var self: Int` is invalid). - `backtickIfNeededForMemberAccess()` — `.name` position. Most keywords are valid bare (`obj.class`, `.break`); `self` is the exception because `obj.self` is the identity expression, not a member access, so a property named `self` must be written as `` obj.`self` ``. - `backtickIfNeededForLocalReference()` — body references. `self` is valid bare (refers to the parameter); other keywords still need escaping. Parameter *declarations* (`_ name: Type`) stay bare for every keyword except `inout`. All helpers are idempotent (no double-wrapping) and escape dotted paths per-component. Names are normalized (backticks stripped) at extraction in `SwiftToSkeleton` so ABI/JSON/JS names are clean. Adds `IdentifierEscapingTests` pinning the rules against `isValidSwiftIdentifier(for:)`, a `KeywordNames` codegen snapshot covering all positions, and macro tests for keyword names under `@JSFunction`/`@JSGetter`/`@JSSetter`. 200 tests pass; no existing snapshots changed. --- .../Sources/BridgeJSCore/ExportSwift.swift | 79 +-- .../Sources/BridgeJSCore/ImportTS.swift | 61 ++- .../BridgeJSCore/SwiftToSkeleton.swift | 44 +- .../JSFunctionMacroTests.swift | 19 + .../JSGetterMacroTests.swift | 20 + .../JSSetterMacroTests.swift | 18 + .../IdentifierEscapingTests.swift | 145 ++++++ .../Inputs/MacroSwift/KeywordNames.swift | 59 +++ .../BridgeJSCodegenTests/KeywordNames.json | 403 ++++++++++++++++ .../BridgeJSCodegenTests/KeywordNames.swift | 322 +++++++++++++ .../BridgeJSLinkTests/KeywordNames.d.ts | 65 +++ .../BridgeJSLinkTests/KeywordNames.js | 455 ++++++++++++++++++ 12 files changed, 1630 insertions(+), 60 deletions(-) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/IdentifierEscapingTests.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/KeywordNames.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/KeywordNames.json create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/KeywordNames.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/KeywordNames.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/KeywordNames.js diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 2cc551857..1c4961185 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -209,12 +209,21 @@ public class ExportSwift { func liftParameter(param: Parameter) throws { parameters.append(param) let liftingInfo = try param.type.liftParameterInfo() - let argumentsToLift: [String] + // `referenceNames` are used in the function body to forward the parameter + // into a lifting call, so keyword names (e.g. `in`) must be escaped here. + // `declarationNames` are the ABI parameter declaration names, which are valid + // bare for every keyword except `inout`, so they stay unescaped. + let referenceNames: [String] + let declarationNames: [String] if liftingInfo.parameters.count == 1 { - argumentsToLift = [param.name] + referenceNames = [param.name.backtickIfNeededForLocalReference()] + declarationNames = [param.name] } else { - argumentsToLift = liftingInfo.parameters.map { (name, _) in param.name + name.capitalizedFirstLetter } + let synthesized = liftingInfo.parameters.map { (name, _) in param.name + name.capitalizedFirstLetter } + referenceNames = synthesized + declarationNames = synthesized } + let argumentsToLift = referenceNames let typeNameForIntrinsic: String let liftingExpr: ExprSyntax @@ -222,7 +231,7 @@ 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.name.backtickIfNeededForLocalReference()))") case .swiftStruct(let structName): typeNameForIntrinsic = structName liftingExpr = ExprSyntax("\(raw: structName).bridgeJSLiftParameter()") @@ -248,7 +257,7 @@ public class ExportSwift { } liftedParameterExprs.append(liftingExpr) - for (name, type) in zip(argumentsToLift, liftingInfo.parameters.map { $0.type }) { + for (name, type) in zip(declarationNames, liftingInfo.parameters.map { $0.type }) { abiParameterSignatures.append((name, type)) } } @@ -560,15 +569,15 @@ public class ExportSwift { func callName(for property: ExportedProperty) -> String { switch self { case .enumStatic(let enumDef): - return "\(enumDef.swiftCallName).\(property.name)" + return "\(enumDef.swiftCallName).\(property.name.backtickIfNeededForMemberAccess())" case .classStatic(let klass): // property.callName() would use staticContext (the ABI name) as prefix; // use swiftCallName directly so the emitted expression is valid Swift. - return "\(klass.swiftCallName).\(property.name)" + return "\(klass.swiftCallName).\(property.name.backtickIfNeededForMemberAccess())" case .classInstance: - return property.callName() + return property.name.backtickIfNeededForMemberAccess() case .structStatic(let structDef): - return "\(structDef.swiftCallName).\(property.name)" + return "\(structDef.swiftCallName).\(property.name.backtickIfNeededForMemberAccess())" } } } @@ -626,7 +635,7 @@ public class ExportSwift { if isStatic { let klassName = callName.components(separatedBy: ".").dropLast().joined(separator: ".") - setterBuilder.callStaticPropertySetter(klassName: klassName, propertyName: property.name) + setterBuilder.callStaticPropertySetter(klassName: klassName, propertyName: property.name.backtickIfNeededForMemberAccess()) } else { setterBuilder.callPropertySetter(propertyName: callName) } @@ -645,10 +654,10 @@ public class ExportSwift { } if function.effects.isStatic, let staticContext = function.staticContext { - let callName = "\(staticContextBaseName(staticContext)).\(function.name)" + let callName = "\(staticContextBaseName(staticContext).backtickIfNeeded()).\(function.name.backtickIfNeededForMemberAccess())" builder.call(name: callName, returnType: function.returnType) } else { - builder.call(name: function.name, returnType: function.returnType) + builder.call(name: function.name.backtickIfNeeded(), returnType: function.returnType) } try builder.lowerReturnValue(returnType: function.returnType) @@ -691,9 +700,9 @@ public class ExportSwift { } if method.effects.isStatic { - builder.call(name: "\(ownerTypeName).\(method.name)", returnType: method.returnType) + builder.call(name: "\(ownerTypeName).\(method.name.backtickIfNeededForMemberAccess())", returnType: method.returnType) } else { - builder.callMethod(methodName: method.name, returnType: method.returnType) + builder.callMethod(methodName: method.name.backtickIfNeededForMemberAccess(), returnType: method.returnType) } try builder.lowerReturnValue(returnType: method.returnType) return builder.render(abiName: method.abiName) @@ -1104,7 +1113,7 @@ struct EnumCodegen { for (index, enumCase) in enumDef.cases.enumerated() { printer.write("case \(index):") printer.indent { - printer.write("self = .\(enumCase.name)") + printer.write("self = .\(enumCase.name.backtickIfNeededForMemberAccess())") } } printer.write("default:") @@ -1120,7 +1129,7 @@ struct EnumCodegen { printer.indent { printer.write("switch self {") for (index, enumCase) in enumDef.cases.enumerated() { - printer.write("case .\(enumCase.name):") + printer.write("case .\(enumCase.name.backtickIfNeededForMemberAccess()):") printer.indent { printer.write("return \(index)") } @@ -1186,7 +1195,7 @@ struct EnumCodegen { if enumCase.associatedValues.isEmpty { printer.write("case \(caseIndex):") printer.indent { - printer.write("return .\(enumCase.name)") + printer.write("return .\(enumCase.name.backtickIfNeededForMemberAccess())") } } else { printer.write("case \(caseIndex):") @@ -1201,7 +1210,7 @@ struct EnumCodegen { return "\(labelPrefix)\(liftExpr)" } printer.indent { - printer.write("return .\(enumCase.name)(\(argList.joined(separator: ", ")))") + printer.write("return .\(enumCase.name.backtickIfNeededForMemberAccess())(\(argList.joined(separator: ", ")))") } } } @@ -1215,7 +1224,7 @@ struct EnumCodegen { let paramName = associatedValue.label ?? "param\(index)" let statements = stackCodegen.lowerStatements( for: associatedValue.type, - accessor: paramName, + accessor: paramName.backtickIfNeededForLocalReference(), varPrefix: paramName ) for statement in statements { @@ -1227,15 +1236,15 @@ struct EnumCodegen { private func generateReturnSwitchCases(printer: CodeFragmentPrinter, enumDef: ExportedEnum) { for (caseIndex, enumCase) in enumDef.cases.enumerated() { if enumCase.associatedValues.isEmpty { - printer.write("case .\(enumCase.name):") + printer.write("case .\(enumCase.name.backtickIfNeededForMemberAccess()):") printer.indent { printer.write("return Int32(\(caseIndex))") } } else { let pattern = enumCase.associatedValues.enumerated() - .map { index, associatedValue in "let \(associatedValue.label ?? "param\(index)")" } + .map { index, associatedValue in "let \((associatedValue.label ?? "param\(index)").backtickIfNeeded())" } .joined(separator: ", ") - printer.write("case .\(enumCase.name)(\(pattern)):") + printer.write("case .\(enumCase.name.backtickIfNeededForMemberAccess())(\(pattern)):") printer.indent { generatePayloadPushingCode(printer: printer, associatedValues: enumCase.associatedValues) // Push tag AFTER payloads so it's popped first (LIFO) by the JS lift function. @@ -1332,12 +1341,12 @@ struct StructCodegen { let instanceProps = structDef.properties.filter { !$0.isStatic } for property in instanceProps.reversed() { - let fieldName = property.name + let fieldName = property.name.backtickIfNeeded() let liftExpr = stackCodegen.liftExpression(for: property.type) lines.append("let \(fieldName) = \(liftExpr)") } - let initArgs = instanceProps.map { "\($0.name): \($0.name)" }.joined(separator: ", ") + let initArgs = instanceProps.map { "\($0.name): \($0.name.backtickIfNeededForLocalReference())" }.joined(separator: ", ") lines.append("return \(structDef.swiftCallName)(\(initArgs))") return lines @@ -1350,7 +1359,7 @@ struct StructCodegen { for property in instanceProps { let statements = stackCodegen.lowerStatements( for: property.type, - accessor: "self.\(property.name)", + accessor: "self.\(property.name.backtickIfNeededForMemberAccess())", varPrefix: property.name ) for statement in statements { @@ -1418,7 +1427,7 @@ struct ProtocolCodegen { ) externDecls.append(DeclSyntax("\(raw: externDeclPrinter.lines.joined(separator: "\n"))")) let methodImplPrinter = CodeFragmentPrinter() - methodImplPrinter.write("func \(method.name)\(signature) {") + methodImplPrinter.write("func \(method.name.backtickIfNeeded())\(signature) {") methodImplPrinter.indent { methodImplPrinter.write(lines: builder.body.lines) } @@ -1441,7 +1450,7 @@ struct ProtocolCodegen { } let structDeclPrinter = CodeFragmentPrinter() - structDeclPrinter.write("struct \(wrapperName): \(protocolName), _BridgedSwiftProtocolWrapper {") + structDeclPrinter.write("struct \(wrapperName): \(protocolName.backtickIfNeeded()), _BridgedSwiftProtocolWrapper {") structDeclPrinter.indent { structDeclPrinter.write("let jsObject: JSObject") structDeclPrinter.nextLine() @@ -1509,7 +1518,7 @@ struct ProtocolCodegen { let getterExternDecl = DeclSyntax("\(raw: getterExternDeclPrinter.lines.joined(separator: "\n"))") var externDecls: [DeclSyntax] = [getterExternDecl] - printer.write("var \(property.name): \(property.type.swiftType) {") + printer.write("var \(property.name.backtickIfNeeded()): \(property.type.swiftType) {") try printer.indent { printer.write("get {") printer.indent { @@ -1612,19 +1621,19 @@ extension BridgeType { case .jsValue: return "JSValue" case .jsObject(nil): return "JSObject" case .jsObject(let name?): return name - case .swiftHeapObject(let name): return name + case .swiftHeapObject(let name): return name.backtickIfNeeded() case .unsafePointer(let ptr): return ptr.swiftType - case .swiftProtocol(let name): return "Any\(name)" + case .swiftProtocol(let name): return "Any\(name.backtickIfNeeded())" case .void: return "Void" case .nullable(let wrappedType, let kind): return kind == .null ? "Optional<\(wrappedType.swiftType)>" : "JSUndefinedOr<\(wrappedType.swiftType)>" case .array(let elementType): return "[\(elementType.swiftType)]" case .dictionary(let valueType): return "[String: \(valueType.swiftType)]" - case .caseEnum(let name): return name - case .rawValueEnum(let name, _): return name - case .associatedValueEnum(let name): return name - case .swiftStruct(let name): return name - case .namespaceEnum(let name): return name + case .caseEnum(let name): return name.backtickIfNeeded() + case .rawValueEnum(let name, _): return name.backtickIfNeeded() + case .associatedValueEnum(let name): return name.backtickIfNeeded() + case .swiftStruct(let name): return name.backtickIfNeeded() + case .namespaceEnum(let name): return name.backtickIfNeeded() case .closure(let signature, let useJSTypedClosure): let paramTypes = signature.parameters.map { $0.swiftType }.joined(separator: ", ") let effectsStr = (signature.isAsync ? " async" : "") + (signature.isThrows ? " throws" : "") diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 474f1a75f..3997ca9ef 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -1,4 +1,5 @@ import SwiftBasicFormat +import SwiftParser import SwiftSyntax import SwiftSyntaxBuilder #if canImport(BridgeJSSkeleton) @@ -148,10 +149,10 @@ public struct ImportTS { switch param.type { case .closure(let signature, useJSTypedClosure: false): let jsTypedClosureType = BridgeType.closure(signature, useJSTypedClosure: true).swiftType - body.write("let \(param.name) = \(jsTypedClosureType)(\(param.name))") + body.write("let \(param.name.backtickIfNeededForLocalReference()) = \(jsTypedClosureType)(\(param.name.backtickIfNeededForLocalReference()))") // The just created JSObject is not owned by the caller unlike those passed in parameters, // so we need to extend its lifetime during the call to ensure the JSObject.id is valid. - valuesToExtendLifetimeDuringCall.append(param.name) + valuesToExtendLifetimeDuringCall.append(param.name.backtickIfNeededForLocalReference()) default: break } @@ -172,7 +173,7 @@ public struct ImportTS { if loweringInfo.useBorrowing { let returnVariableName = "ret\(borrowedArguments.count)" let assign = needsReturnVariable ? "let \(returnVariableName) = " : "" - body.write("\(assign)\(param.name).bridgeJSWithLoweredParameter { \(pattern) in") + body.write("\(assign)\(param.name.backtickIfNeededForLocalReference()).bridgeJSWithLoweredParameter { \(pattern) in") body.indent() borrowedArguments.append( BorrowedArgument( @@ -185,10 +186,10 @@ public struct ImportTS { ) } else if case .nullable(.swiftProtocol, _) = param.type, context == .exportSwift { body.write("let \(pattern): (Int32, Int32)") - body.write("if let \(param.name) {") + body.write("if let \(param.name.backtickIfNeededForLocalReference()) {") body.indent { body.write( - "\(pattern) = (1, (\(param.name) as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn())" + "\(pattern) = (1, (\(param.name.backtickIfNeededForLocalReference()) as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn())" ) } body.write("} else {") @@ -200,10 +201,10 @@ public struct ImportTS { let initializerExpr: ExprSyntax if case .swiftProtocol = param.type, context == .exportSwift { initializerExpr = ExprSyntax( - "(\(raw: param.name) as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn()" + "(\(raw: param.name.backtickIfNeededForLocalReference()) as! _BridgedSwiftProtocolExportable).bridgeJSLowerAsProtocolReturn()" ) } else { - initializerExpr = ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()") + initializerExpr = ExprSyntax("\(raw: param.name.backtickIfNeededForLocalReference()).bridgeJSLowerParameter()") } let binding = loweringInfo.loweredParameters.isEmpty ? "_" : pattern @@ -684,6 +685,9 @@ struct SwiftSignatureBuilder { let label = param.label ?? param.name let paramType = buildParameterTypeSyntax(from: param.type) + // Parameter names are valid bare for every keyword except `inout` (which the + // parser refuses as an argument label). Don't escape them: `_ where: Int` and + // `_ self: Int` are both legal Swift, and backticks here would only add noise. if useWildcardLabels { // Always use wildcard labels: "_ name: Type" return "_ \(param.name): \(paramType)" @@ -839,7 +843,7 @@ enum SwiftCodePattern { ) printer.write("@inline(never) fileprivate func \(functionName)\(signature) {") printer.indent { - printer.write("return \(inModuleDeclName)(\(parameterNames.joined(separator: ", ")))") + printer.write("return \(inModuleDeclName)(\(parameterNames.map { $0.backtickIfNeededForLocalReference() }.joined(separator: ", ")))") } printer.write("}") } @@ -1034,7 +1038,46 @@ extension BridgeType { } extension String { + /// Escapes a name for use in a *declaration* position: `var`/`let`/property + /// declarations, `let` binding patterns, and type-name positions. Keywords are + /// not valid bare here — `var class: Int` and `let where = …` are invalid — so + /// every keyword (including `self`) is escaped. + /// + /// Escapes each dotted path component independently (qualified type paths such + /// as `Outer.Inner` stay valid while keyword components get escaped) and is + /// idempotent (surrounding backticks are stripped first so inputs that already + /// carry escaping are not double-wrapped). func backtickIfNeeded() -> String { - return self.isValidSwiftIdentifier(for: .variableName) ? self : "`\(self)`" + escapeComponents { $0.escapeSingleIdentifier(using: .variableName) } + } + + /// Escapes a name for use in *member access* position (`.name`). Most keywords + /// are valid bare here — `obj.class`, `obj.where`, `.break` all compile — so only + /// names that are *not* valid member-access identifiers get backticks. The + /// notable case is `self`: `obj.self` is the identity expression (returns `obj`), + /// not an access of a property named `self`, so a property literally named `self` + /// must be written as `` obj.`self` ``. + func backtickIfNeededForMemberAccess() -> String { + escapeComponents { $0.escapeSingleIdentifier(using: .memberAccess) } + } + + /// Escapes a name for use as a *local reference* in expression position (e.g. a + /// parameter or local variable referenced inside a generated function body). + /// `self` is valid bare here — it refers to the parameter — so it is not escaped; + /// other keywords (`class`, `where`, …) still need backticks because they cannot + /// appear as a bare identifier expression. + func backtickIfNeededForLocalReference() -> String { + if self == "self" { return self } + return backtickIfNeeded() + } + + private func escapeComponents(_ escape: (String) -> String) -> String { + self.split(separator: ".").map { String($0) }.map(escape).joined(separator: ".") + } + + private func escapeSingleIdentifier(using context: SwiftParser.IdentifierCheckContext) -> String { + let stripped = + (hasPrefix("`") && hasSuffix("`") && count > 2) ? String(dropFirst().dropLast()) : self + return stripped.isValidSwiftIdentifier(for: context) ? stripped : "`\(stripped)`" } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index d327de307..9ee149a2f 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -1207,8 +1207,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { continue } - let name = param.secondName?.text ?? param.firstName.text - let label = param.firstName.text + let name = SwiftToSkeleton.normalizeIdentifier(param.secondName?.text ?? param.firstName.text) + let label = SwiftToSkeleton.normalizeIdentifier(param.firstName.text) let defaultValue: DefaultValue? if allowDefaults { @@ -1290,7 +1290,9 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { return nil } - let name = node.name.text + // Strip backticks from keyword-escaped identifiers (e.g. `` `default` `` -> "default") + // so they don't leak into ABI names (`bjs_`default`` is invalid) or JS-side names. + let name = SwiftToSkeleton.normalizeIdentifier(node.name.text) let attributeNamespace = extractNamespace(from: jsAttribute) let computedNamespace = computeNamespace(for: node) @@ -1643,7 +1645,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { continue } - let propertyName = pattern.identifier.text + let propertyName = SwiftToSkeleton.normalizeIdentifier(pattern.identifier.text) guard let typeAnnotation = binding.typeAnnotation else { diagnose(node: binding, message: "@JS property must have explicit type annotation") @@ -1687,7 +1689,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - let name = node.name.text + let name = SwiftToSkeleton.normalizeIdentifier(node.name.text) guard let jsAttribute = node.attributes.firstJSAttribute else { return .skipChildren @@ -1848,7 +1850,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { return .skipChildren } - let name = node.name.text + let name = SwiftToSkeleton.normalizeIdentifier(node.name.text) let rawType: String? = node.inheritanceClause?.inheritedTypes.first { inheritedType in let typeName = inheritedType.type.trimmedDescription @@ -1968,7 +1970,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { return .skipChildren } - let name = node.name.text + let name = SwiftToSkeleton.normalizeIdentifier(node.name.text) let namespaceResult = resolveNamespace(from: jsAttribute, for: node, declarationType: "protocol") guard namespaceResult.isValid else { @@ -2036,7 +2038,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { return .skipChildren } - let name = node.name.text + let name = SwiftToSkeleton.normalizeIdentifier(node.name.text) let namespaceResult = resolveNamespace(from: jsAttribute, for: node, declarationType: "struct") guard namespaceResult.isValid else { @@ -2075,7 +2077,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { continue } - let fieldName = pattern.identifier.text + let fieldName = SwiftToSkeleton.normalizeIdentifier(pattern.identifier.text) guard let typeAnnotation = binding.typeAnnotation else { diagnose(node: binding, message: "Struct field must have explicit type annotation") @@ -2169,7 +2171,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { protocolName: String, namespace: [String]? ) -> ExportedFunction? { - let name = node.name.text + let name = SwiftToSkeleton.normalizeIdentifier(node.name.text) let parameters = parseParameters(from: node.signature.parameterClause, allowDefaults: false) @@ -2221,7 +2223,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { continue } - let propertyName = pattern.identifier.text + let propertyName = SwiftToSkeleton.normalizeIdentifier(pattern.identifier.text) guard let typeAnnotation = binding.typeAnnotation else { diagnose(node: binding, message: "Protocol property must have explicit type annotation") @@ -2293,7 +2295,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { } for element in node.elements { - let caseName = element.name.text + let caseName = SwiftToSkeleton.normalizeIdentifier(element.name.text) let rawValue: String? var associatedValues: [AssociatedValue] = [] @@ -2331,7 +2333,12 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { continue } - let label = param.firstName?.text + let label: String? + if let firstName = param.firstName, firstName.text != "_" { + label = SwiftToSkeleton.normalizeIdentifier(firstName.text) + } else { + label = nil + } associatedValues.append(AssociatedValue(label: label, type: bridgeType)) } } @@ -2936,7 +2943,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { validateDefaultExportUsage(extractedJSName, from: from, node: node) let accessLevel = Self.bridgeAccessLevel(from: node.modifiers) enterJSClass( - node.name.text, + SwiftToSkeleton.normalizeIdentifier(node.name.text), jsName: extractedJSName?.memberName, from: from, accessLevel: accessLevel @@ -2959,7 +2966,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { validateDefaultExportUsage(extractedJSName, from: from, node: node) let accessLevel = Self.bridgeAccessLevel(from: node.modifiers) enterJSClass( - node.name.text, + SwiftToSkeleton.normalizeIdentifier(node.name.text), jsName: extractedJSName?.memberName, from: from, accessLevel: accessLevel @@ -3274,7 +3281,12 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { let nameToken = param.secondName ?? param.firstName let name = SwiftToSkeleton.normalizeIdentifier(nameToken.text) let labelToken = param.secondName == nil ? nil : param.firstName - let label = labelToken?.text == "_" ? nil : labelToken?.text + let label: String? + if let labelToken, labelToken.text != "_" { + label = SwiftToSkeleton.normalizeIdentifier(labelToken.text) + } else { + label = nil + } return Parameter(label: label, name: name, type: bridgeType) } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift index 28eade958..047e4b9e8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift @@ -523,6 +523,25 @@ import BridgeJSMacros ) } + @Test func keywordEscapedNameIsNormalized() { + // A backtick-escaped Swift keyword (e.g. `` `default` ``) must be normalized when + // building the glue name: the thunk is `_$default`, not `_$`default``, while the + // call site stays valid Swift because `_$default` is a regular identifier. + TestSupport.assertMacroExpansion( + """ + @JSFunction + func `default`() throws(JSException) -> Void + """, + expandedSource: """ + func `default`() throws(JSException) -> Void { + try _$default() + } + """, + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, + ) + } + @Test func functionWithExistingBody() { TestSupport.assertMacroExpansion( """ diff --git a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSGetterMacroTests.swift b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSGetterMacroTests.swift index 0e4cea844..0583b0d32 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSGetterMacroTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSGetterMacroTests.swift @@ -366,4 +366,24 @@ import BridgeJSMacros indentationWidth: indentationWidth, ) } + + @Test func keywordEscapedNameIsNormalized() { + // A backtick-escaped Swift keyword property name must be normalized in the + // glue name: `_$`self`_get` would be invalid, `_$self_get` is correct. + TestSupport.assertMacroExpansion( + """ + @JSGetter + var `self`: String + """, + expandedSource: """ + var `self`: String { + get throws(JSException) { + return try _$self_get() + } + } + """, + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, + ) + } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSSetterMacroTests.swift b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSSetterMacroTests.swift index 00d959921..6f313c2dc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSSetterMacroTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSSetterMacroTests.swift @@ -465,4 +465,22 @@ import BridgeJSMacros indentationWidth: indentationWidth, ) } + + @Test func setterWithBacktickEscapedNameIsStripped() { + // A backtick-escaped setter name (e.g. `` `setFoo` ``) must be normalized before + // deriving the property name: the glue is `_$foo_set`, not `_$`foo`_set`. + TestSupport.assertMacroExpansion( + """ + @JSSetter + func `setFoo`(_ value: Int) throws(JSException) + """, + expandedSource: """ + func `setFoo`(_ value: Int) throws(JSException) { + try _$foo_set(value) + } + """, + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, + ) + } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/IdentifierEscapingTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/IdentifierEscapingTests.swift new file mode 100644 index 000000000..3512d4595 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/IdentifierEscapingTests.swift @@ -0,0 +1,145 @@ +import Foundation +import SwiftParser +import Testing +@testable import BridgeJSCore + +/// The codegenerator emits user-supplied names (which may be Swift keywords escaped +/// with backticks in source, e.g. `` `default` ``) into three syntactically distinct +/// positions, each with different escaping rules. These tests pin the rules so the +/// generated Swift is always valid and as noise-free as possible. +/// +/// See `String.backtickIfNeeded*` in `ImportTS.swift` for the implementation, and +/// `SwiftParser.IsValidIdentifier` for the underlying `isValidSwiftIdentifier(for:)` +/// semantics the contexts are built on. +@Suite struct IdentifierEscapingTests { + // MARK: - backtickIfNeeded() — declaration position + + /// In `var`/`let`/property/`let`-binding declarations, keywords are not valid + /// bare identifiers and must be escaped. `self` is no exception: `var self: Int` + /// is invalid. + @Test(arguments: [ + "class", "where", "in", "case", "continue", "break", "return", "switch", + "as", "default", "do", "for", "if", "else", "self", "init", + "subscript", "nil", "true", "false", + ]) + func declarationEscapesKeywords(_ keyword: String) { + let escaped = keyword.backtickIfNeeded() + #expect(escaped == "`\(keyword)`") + // The escaped form must itself be a valid declaration identifier. + #expect(escaped.isValidSwiftIdentifier(for: .variableName)) + } + + @Test(arguments: ["foo", "myVar", "Parser", "_internal", "x", "URL"]) + func declarationLeavesRegularIdentifiersBare(_ name: String) { + #expect(name.backtickIfNeeded() == name) + } + + // MARK: - backtickIfNeededForMemberAccess() — `.name` position + + /// In member access position (`.name`), most keywords are valid bare: `obj.class`, + /// `.break`, `obj.where` all compile. `self` is the exception — `obj.self` is the + /// identity expression (returns `obj`), not an access of a property named `self`, + /// so a property literally named `self` must be written as `` obj.`self` ``. + @Test(arguments: [ + "class", "where", "in", "case", "continue", "break", "return", "switch", + "as", "default", "delete", "do", "for", "if", "else", + ]) + func memberAccessLeavesKeywordsBare(_ keyword: String) { + // These compile bare in member position (`t.` parses as member access). + #expect(keyword.backtickIfNeededForMemberAccess() == keyword) + #expect(keyword.isValidSwiftIdentifier(for: .memberAccess)) + } + + @Test func memberAccessEscapesSelf() { + // `obj.self` is the identity expression, not a member named `self`. + #expect("self".backtickIfNeededForMemberAccess() == "`self`") + #expect(!"self".isValidSwiftIdentifier(for: .memberAccess)) + } + + @Test(arguments: ["foo", "myVar", "Parser", "_internal", "value"]) + func memberAccessLeavesRegularIdentifiersBare(_ name: String) { + #expect(name.backtickIfNeededForMemberAccess() == name) + } + + // MARK: - backtickIfNeededForLocalReference() — body reference position + + /// When referencing a parameter or local variable inside a generated function + /// body, `self` is valid bare (it refers to the parameter), but other keywords + /// still need escaping because they cannot appear as a bare identifier expression. + @Test func localReferenceLeavesSelfBare() { + #expect("self".backtickIfNeededForLocalReference() == "self") + } + + @Test(arguments: [ + "class", "where", "in", "case", "continue", "break", "return", "switch", + "as", "default", "do", "for", "if", "else", + ]) + func localReferenceEscapesOtherKeywords(_ keyword: String) { + #expect(keyword.backtickIfNeededForLocalReference() == "`\(keyword)`") + } + + @Test(arguments: ["foo", "myVar", "value", "_internal"]) + func localReferenceLeavesRegularIdentifiersBare(_ name: String) { + #expect(name.backtickIfNeededForLocalReference() == name) + } + + // MARK: - Idempotency & dotted paths + + @Test(arguments: [ + "class", "self", "where", "foo", "`already`", "`self`", + ]) + func escapingIsIdempotent(_ name: String) { + let once = name.backtickIfNeeded() + #expect(once.backtickIfNeeded() == once) + let memberOnce = name.backtickIfNeededForMemberAccess() + #expect(memberOnce.backtickIfNeededForMemberAccess() == memberOnce) + let localOnce = name.backtickIfNeededForLocalReference() + #expect(localOnce.backtickIfNeededForLocalReference() == localOnce) + } + + /// A name already wrapped in backticks (e.g. straight from `TokenSyntax.text`) + /// must not be double-wrapped. + @Test func alreadyBacktickedNameIsNotDoubleWrapped() { + #expect("`class`".backtickIfNeeded() == "`class`") + #expect("`self`".backtickIfNeededForMemberAccess() == "`self`") + } + + /// Qualified type paths (`Outer.Inner`) escape each component independently so a + /// keyword component is escaped while regular components stay bare. + @Test func dottedPathEscapesPerComponent() { + #expect("Outer.Inner".backtickIfNeeded() == "Outer.Inner") + #expect("Outer.`class`".backtickIfNeeded() == "Outer.`class`") + // A keyword second component gets escaped; the base stays bare. + #expect("Utils.where".backtickIfNeeded() == "Utils.`where`") + // Member-access context leaves `where` bare, so the whole path stays bare. + #expect("Utils.where".backtickIfNeededForMemberAccess() == "Utils.where") + // `self` as a component is escaped in both contexts (declaration escapes it; + // member access escapes it because `t.self` is the identity expression). + #expect("self.foo".backtickIfNeeded() == "`self`.foo") + #expect("self.foo".backtickIfNeededForMemberAccess() == "`self`.foo") + } + + // MARK: - Consistency with SwiftParser's encoding + + /// The escaping must agree with `isValidSwiftIdentifier(for:)`: a name is emitted + /// bare exactly when SwiftParser considers it a valid identifier in that context. + /// This pins the helpers to swift-syntax's grammar rather than a hand-maintained + /// keyword list. + @Test(arguments: [ + "foo", "self", "class", "where", "in", "case", "continue", "break", + "return", "switch", "as", "default", "delete", "do", "for", "if", "else", + "init", "subscript", "nil", "true", "false", "_internal", + ]) + func escapingAgreesWithIsValidSwiftIdentifier(_ name: String) { + let declared = name.backtickIfNeeded() + #expect(declared == name || !name.isValidSwiftIdentifier(for: .variableName)) + #expect(declared.hasPrefix("`") == !name.isValidSwiftIdentifier(for: .variableName)) + + let member = name.backtickIfNeededForMemberAccess() + #expect(member.hasPrefix("`") == !name.isValidSwiftIdentifier(for: .memberAccess)) + + let local = name.backtickIfNeededForLocalReference() + let localIsValidBare = name == "self" || name.isValidSwiftIdentifier(for: .variableName) + #expect(local.hasPrefix("`") == !localIsValidBare) + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/KeywordNames.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/KeywordNames.swift new file mode 100644 index 000000000..a49e401d5 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/KeywordNames.swift @@ -0,0 +1,59 @@ +// Swift identifiers escaped with backticks (e.g. `` `default` ``) must be normalized +// before reaching ABI names: the WebAssembly export name must be `bjs_default`, not +// `bjs_`default``, while the emitted Swift call still escapes the keyword as +// `` `default` ``. This input exercises every member emission path with keyword names. + +// Top-level function: `default` is a common JS default-export name. +@JS func `default`() {} +@JS func `delete`(_ value: Int32) -> Int32 { value } + +// Class: instance + static methods, instance + static properties, init. +@JS class `Parser` { + @JS var `in`: Int + @JS static var `self`: String { "parser" } + + @JS init(`in`: Int) { + self.`in` = `in` + } + + @JS func `class`() -> Int { + return `in` + } + + @JS static func `where`(_ value: Int) -> Int { + return value + } +} + +// Struct: keyword-named stored fields + instance method. +@JS struct `Record` { + var `case`: Int + var `continue`: String + + @JS init(`case`: Int, `continue`: String) { + self.`case` = `case` + self.`continue` = `continue` + } + + @JS func `as`() -> String { + return `continue` + } +} + +// Enum: keyword-named cases + static method. +@JS enum `Token` { + case `break` + case `return`(String) + + @JS static func `switch`(_ value: Int) -> Int { + return value + } +} + +// Protocol: keyword-named properties (get / get set) and methods. +@JS protocol `Observer` { + var `do`: Int { get set } + var `for`: String { get } + func `if`(_ value: Int) -> Bool + func `else`() -> String +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/KeywordNames.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/KeywordNames.json new file mode 100644 index 000000000..fb4732846 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/KeywordNames.json @@ -0,0 +1,403 @@ +{ + "exported" : { + "aliases" : [ + + ], + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_Parser_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "in", + "name" : "in", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "methods" : [ + { + "abiName" : "bjs_Parser_class", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "class", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_Parser_static_where", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "where", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + }, + "staticContext" : { + "className" : { + "_0" : "Parser" + } + } + } + ], + "name" : "Parser", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "in", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : true, + "name" : "self", + "staticContext" : { + "className" : { + "_0" : "Parser" + } + }, + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "Parser" + } + ], + "enums" : [ + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "break" + }, + { + "associatedValues" : [ + { + "type" : { + "string" : { + + } + } + } + ], + "name" : "return" + } + ], + "emitStyle" : "const", + "name" : "Token", + "staticMethods" : [ + { + "abiName" : "bjs_Token_static_switch", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "switch", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + }, + "staticContext" : { + "enumName" : { + "_0" : "Token" + } + } + } + ], + "staticProperties" : [ + + ], + "swiftCallName" : "Token", + "tsFullPath" : "Token" + } + ], + "exposeToGlobal" : false, + "functions" : [ + { + "abiName" : "bjs_default", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "default", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_delete", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "delete", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "w32" + } + } + } + } + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "w32" + } + } + } + } + ], + "protocols" : [ + { + "methods" : [ + { + "abiName" : "bjs_Observer_if", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "if", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "bool" : { + + } + } + }, + { + "abiName" : "bjs_Observer_else", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "else", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } + } + ], + "name" : "Observer", + "properties" : [ + { + "isReadonly" : false, + "name" : "do", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "isReadonly" : true, + "name" : "for", + "type" : { + "string" : { + + } + } + } + ] + } + ], + "structs" : [ + { + "constructor" : { + "abiName" : "bjs_Record_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "case", + "name" : "case", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "label" : "continue", + "name" : "continue", + "type" : { + "string" : { + + } + } + } + ] + }, + "methods" : [ + { + "abiName" : "bjs_Record_as", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "as", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } + } + ], + "name" : "Record", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "case", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "continue", + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "Record" + } + ] + }, + "moduleName" : "TestModule", + "usedExternalModules" : [ + + ] +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/KeywordNames.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/KeywordNames.swift new file mode 100644 index 000000000..0c1ba2ea6 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/KeywordNames.swift @@ -0,0 +1,322 @@ +struct AnyObserver: Observer, _BridgedSwiftProtocolWrapper { + let jsObject: JSObject + + func `if`(_ value: Int) -> Bool { + let valueValue = value.bridgeJSLowerParameter() + let jsObjectValue = jsObject.bridgeJSLowerParameter() + let ret = _extern_if(jsObjectValue, valueValue) + return Bool.bridgeJSLiftReturn(ret) + } + + func `else`() -> String { + let jsObjectValue = jsObject.bridgeJSLowerParameter() + let ret = _extern_else(jsObjectValue) + return String.bridgeJSLiftReturn(ret) + } + + var `do`: Int { + get { + let jsObjectValue = jsObject.bridgeJSLowerParameter() + let ret = bjs_Observer_do_get(jsObjectValue) + return Int.bridgeJSLiftReturn(ret) + } + set { + let newValueValue = newValue.bridgeJSLowerParameter() + let jsObjectValue = jsObject.bridgeJSLowerParameter() + bjs_Observer_do_set(jsObjectValue, newValueValue) + } + } + + var `for`: String { + get { + let jsObjectValue = jsObject.bridgeJSLowerParameter() + let ret = bjs_Observer_for_get(jsObjectValue) + return String.bridgeJSLiftReturn(ret) + } + } + + static func bridgeJSLiftParameter(_ value: Int32) -> Self { + return AnyObserver(jsObject: JSObject(id: UInt32(bitPattern: value))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_Observer_if") +fileprivate func _extern_if_extern(_ jsObject: Int32, _ value: Int32) -> Int32 +#else +fileprivate func _extern_if_extern(_ jsObject: Int32, _ value: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _extern_if(_ jsObject: Int32, _ value: Int32) -> Int32 { + return _extern_if_extern(jsObject, value) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_Observer_else") +fileprivate func _extern_else_extern(_ jsObject: Int32) -> Int32 +#else +fileprivate func _extern_else_extern(_ jsObject: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _extern_else(_ jsObject: Int32) -> Int32 { + return _extern_else_extern(jsObject) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_Observer_do_get") +fileprivate func bjs_Observer_do_get_extern(_ jsObject: Int32) -> Int32 +#else +fileprivate func bjs_Observer_do_get_extern(_ jsObject: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_Observer_do_get(_ jsObject: Int32) -> Int32 { + return bjs_Observer_do_get_extern(jsObject) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_Observer_do_set") +fileprivate func bjs_Observer_do_set_extern(_ jsObject: Int32, _ newValue: Int32) -> Void +#else +fileprivate func bjs_Observer_do_set_extern(_ jsObject: Int32, _ newValue: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_Observer_do_set(_ jsObject: Int32, _ newValue: Int32) -> Void { + return bjs_Observer_do_set_extern(jsObject, newValue) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_Observer_for_get") +fileprivate func bjs_Observer_for_get_extern(_ jsObject: Int32) -> Int32 +#else +fileprivate func bjs_Observer_for_get_extern(_ jsObject: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_Observer_for_get(_ jsObject: Int32) -> Int32 { + return bjs_Observer_for_get_extern(jsObject) +} + +extension Token: _BridgedSwiftAssociatedValueEnum { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPopPayload(_ caseId: Int32) -> Token { + switch caseId { + case 0: + return .break + case 1: + return .return(String.bridgeJSStackPop()) + default: + fatalError("Unknown Token case ID: \(caseId)") + } + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPushPayload() -> Int32 { + switch self { + case .break: + return Int32(0) + case .return(let param0): + param0.bridgeJSStackPush() + return Int32(1) + } + } +} + +@_expose(wasm, "bjs_Token_static_switch") +@_cdecl("bjs_Token_static_switch") +public func _bjs_Token_static_switch(_ value: Int32) -> Int32 { + #if arch(wasm32) + let ret = Token.switch(_: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension Record: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> Record { + let `continue` = String.bridgeJSStackPop() + let `case` = Int.bridgeJSStackPop() + return Record(case: `case`, continue: `continue`) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.case.bridgeJSStackPush() + self.continue.bridgeJSStackPush() + } + + init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_Record(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_Record())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_Record") +fileprivate func _bjs_struct_lower_Record_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_Record_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_Record(_ objectId: Int32) -> Void { + return _bjs_struct_lower_Record_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_Record") +fileprivate func _bjs_struct_lift_Record_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_Record_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_Record() -> Int32 { + return _bjs_struct_lift_Record_extern() +} + +@_expose(wasm, "bjs_Record_init") +@_cdecl("bjs_Record_init") +public func _bjs_Record_init(_ case: Int32, _ continueBytes: Int32, _ continueLength: Int32) -> Void { + #if arch(wasm32) + let ret = Record(case: Int.bridgeJSLiftParameter(`case`), continue: String.bridgeJSLiftParameter(continueBytes, continueLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Record_as") +@_cdecl("bjs_Record_as") +public func _bjs_Record_as() -> Void { + #if arch(wasm32) + let ret = Record.bridgeJSLiftParameter().as() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_default") +@_cdecl("bjs_default") +public func _bjs_default() -> Void { + #if arch(wasm32) + `default`() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_delete") +@_cdecl("bjs_delete") +public func _bjs_delete(_ value: Int32) -> Int32 { + #if arch(wasm32) + let ret = delete(_: Int32.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Parser_init") +@_cdecl("bjs_Parser_init") +public func _bjs_Parser_init(_ in: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Parser(in: Int.bridgeJSLiftParameter(`in`)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Parser_class") +@_cdecl("bjs_Parser_class") +public func _bjs_Parser_class(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Parser.bridgeJSLiftParameter(_self).class() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Parser_static_where") +@_cdecl("bjs_Parser_static_where") +public func _bjs_Parser_static_where(_ value: Int32) -> Int32 { + #if arch(wasm32) + let ret = Parser.where(_: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Parser_in_get") +@_cdecl("bjs_Parser_in_get") +public func _bjs_Parser_in_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Parser.bridgeJSLiftParameter(_self).in + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Parser_in_set") +@_cdecl("bjs_Parser_in_set") +public func _bjs_Parser_in_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + Parser.bridgeJSLiftParameter(_self).in = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Parser_static_self_get") +@_cdecl("bjs_Parser_static_self_get") +public func _bjs_Parser_static_self_get() -> Void { + #if arch(wasm32) + let ret = Parser.`self` + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Parser_deinit") +@_cdecl("bjs_Parser_deinit") +public func _bjs_Parser_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension Parser: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_Parser_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_Parser_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_Parser_wrap") +fileprivate func _bjs_Parser_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_Parser_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_Parser_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_Parser_wrap_extern(pointer) +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/KeywordNames.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/KeywordNames.d.ts new file mode 100644 index 000000000..22464568a --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/KeywordNames.d.ts @@ -0,0 +1,65 @@ +// 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 interface Observer { + if(value: number): boolean; + else(): string; + do: number; + readonly for: string; +} + +export const TokenValues: { + readonly Tag: { + readonly Break: 0; + readonly Return: 1; + }; +}; + +export type TokenTag = + { tag: typeof TokenValues.Tag.Break } | { tag: typeof TokenValues.Tag.Return; param0: string } + +export interface Record { + case: number; + continue: string; + as(): string; +} +export type TokenObject = typeof TokenValues & { + switch(value: number): number; +}; + +/// Represents a Swift heap object like a class instance or an actor instance. +export interface SwiftHeapObject { + /// Release the heap object. + /// + /// Note: Calling this method will release the heap object and it will no longer be accessible. + release(): void; +} +export interface Parser extends SwiftHeapObject { + class(): number; + in: number; +} +export type Exports = { + default(): void; + delete(value: number): number; + Token: TokenObject + Parser: { + new(in: number): Parser; + where(value: number): number; + readonly self: string; + }, + Record: { + init(case: number, continue: string): Record; + }, +} +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/KeywordNames.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/KeywordNames.js new file mode 100644 index 000000000..af45ee239 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/KeywordNames.js @@ -0,0 +1,455 @@ +// 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 const TokenValues = { + Tag: { + Break: 0, + Return: 1, + }, +}; +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 i64Stack = []; + let f32Stack = []; + let f64Stack = []; + let ptrStack = []; + let taStack = []; + const enumHelpers = {}; + const structHelpers = {}; + + let _exports = null; + let bjs = null; + const __bjs_createRecordHelpers = () => ({ + lower: (value) => { + i32Stack.push((value.case | 0)); + const bytes = textEncoder.encode(value.continue); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + const int = i32Stack.pop(); + const instance1 = { case: int, continue: string }; + instance1.as = function() { + structHelpers.Record.lower(this); + const ret = instance.exports.bjs_Record_as(); + const ret1 = tmpRetString; + tmpRetString = undefined; + return ret1; + }.bind(instance1); + return instance1; + } + }); + const __bjs_createTokenValuesHelpers = () => ({ + lower: (value) => { + const enumTag = value.tag; + switch (enumTag) { + case TokenValues.Tag.Break: { + return TokenValues.Tag.Break; + } + case TokenValues.Tag.Return: { + const bytes = textEncoder.encode(value.param0); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + return TokenValues.Tag.Return; + } + default: throw new Error("Unknown TokenValues tag: " + String(enumTag)); + } + }, + lift: (tag) => { + tag = tag | 0; + switch (tag) { + case TokenValues.Tag.Break: return { tag: TokenValues.Tag.Break }; + case TokenValues.Tag.Return: { + const string = strStack.pop(); + return { tag: TokenValues.Tag.Return, param0: string }; + } + default: throw new Error("Unknown TokenValues tag returned from Swift: " + String(tag)); + } + } + }); + + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + bjs = {}; + importObject["bjs"] = bjs; + 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 >>> 0); + 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 >>> 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(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_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["swift_js_struct_lower_Record"] = function(objectId) { + structHelpers.Record.lower(swift.memory.getObject(objectId)); + } + bjs["swift_js_struct_lift_Record"] = function() { + const value = structHelpers.Record.lift(); + return swift.memory.retain(value); + } + 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, 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) {} + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_Parser_wrap"] = function(pointer) { + const obj = _exports['Parser'].__construct(pointer); + return swift.memory.retain(obj); + }; + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; + TestModule["bjs_Observer_do_get"] = function bjs_Observer_do_get(self) { + try { + let ret = swift.memory.getObject(self).do; + return ret; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_Observer_do_set"] = function bjs_Observer_do_set(self, value) { + try { + swift.memory.getObject(self).do = value; + } catch (error) { + setException(error); + } + } + TestModule["bjs_Observer_for_get"] = function bjs_Observer_for_get(self) { + try { + let ret = swift.memory.getObject(self).for; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_Observer_if"] = function bjs_Observer_if(self, value) { + try { + let ret = swift.memory.getObject(self).if(value); + return ret ? 1 : 0; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_Observer_else"] = function bjs_Observer_else(self) { + try { + let ret = swift.memory.getObject(self).else(); + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } 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 swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => { + if (state.hasReleased) { + return; + } + state.hasReleased = true; + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + }); + + /// Represents a Swift heap object like a class instance or an actor instance. + class SwiftHeapObject { + static __wrap(pointer, deinit, prototype, identityCache) { + pointer = pointer >>> 0; + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); + } + + release() { + const state = this.__swiftHeapObjectState; + if (state.hasReleased) { + return; + } + state.hasReleased = true; + swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + } + } + class Parser extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Parser_deinit, Parser.prototype, null); + } + + constructor(in) { + const ret = instance.exports.bjs_Parser_init(in); + return Parser.__construct(ret); + } + class() { + const ret = instance.exports.bjs_Parser_class(this.pointer); + return ret; + } + static where(value) { + const ret = instance.exports.bjs_Parser_static_where(value); + return ret; + } + get in() { + const ret = instance.exports.bjs_Parser_in_get(this.pointer); + return ret; + } + set in(value) { + instance.exports.bjs_Parser_in_set(this.pointer, value); + } + static get self() { + instance.exports.bjs_Parser_static_self_get(); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + } + const RecordHelpers = __bjs_createRecordHelpers(); + structHelpers.Record = RecordHelpers; + + const TokenHelpers = __bjs_createTokenValuesHelpers(); + enumHelpers.Token = TokenHelpers; + + const exports = { + default: function bjs_default() { + instance.exports.bjs_default(); + }, + delete: function bjs_delete(value) { + const ret = instance.exports.bjs_delete(value); + return ret; + }, + Token: { + ...TokenValues, + switch: function(value) { + const ret = instance.exports.bjs_Token_static_switch(value); + return ret; + } + }, + Parser, + Record: { + init: function(case, continue) { + const continueBytes = textEncoder.encode(continue); + const continueId = swift.memory.retain(continueBytes); + instance.exports.bjs_Record_init(case, continueId, continueBytes.length); + const structValue = structHelpers.Record.lift(); + return structValue; + }, + }, + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file