Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
Expand All @@ -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()")
Expand All @@ -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))
}
Expand All @@ -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 =
Expand Down Expand Up @@ -1593,6 +1595,12 @@ struct ProtocolCodegen {
}
}

extension Parameter {
fileprivate var escapedSwiftIdentifier: String {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JS func f(_ in: String) still generates invalid Swift and JS, because parseParameters (SwiftToSkeleton.swift:1590) keeps the backticks in param.name:

public func _bjs_f(_ `in`Bytes: Int32, _ `in`Length: Int32) -> Void {
f: function bjs_f(`in`) {
    const `in`Bytes = textEncoder.encode(`in`);

The import path already strips them with normalizeIdentifier. If this path does the same, the escaping in this PR covers both spellings:

let name = SwiftToSkeleton.normalizeIdentifier(param.secondName?.text ?? param.firstName.text)
@JS func checkEscaped(_ `in`: String) {}

name.backtickIfNeeded()
}
}

extension WasmCoreType {
var swiftType: String {
switch self {
Expand Down
31 changes: 23 additions & 8 deletions Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)"
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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: ", ")
}
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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<String> = [
"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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For @JS protocol Delegate { func handle(in: Int) } the d.ts now has handle(_in: number), but the glue still uses in:

TestModule["bjs_Delegate_handle"] = function bjs_Delegate_handle(self, in) {
func handle(in: Int) -> Void {
    let inValue = in.bridgeJSLowerParameter()

@JSFunction func f(in: Int) has the same problem. It would be good to resolve in this PR, but follow up is fine as well ✌️ The JS side is in ImportedThunkBuilder.liftParameter:

} else if liftingFragment.parameters.count == 1 {
    let name = scope.variable(param.escapedJSIdentifier)
    parameterNames.append(name)
    valuesToLift = [name]
}

The Swift side needs backtickIfNeeded() where CallJSEmission.lowerParameter reads param.name.

Self.javaScriptReservedWords.contains(name) ? "_\(name)" : name
}
}

extension ClosureSignature {
fileprivate func renderTSFunctionType(renderType: (BridgeType) -> String) -> String {
let renderedParameters = parameters.enumerated().map { index, parameter in
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@JS func check(extension: Int, break: Double, function: Bool, in: String) {}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR also changes the constructor and method paths. They generate correctly today, and these cases would keep it that way:

@JS class ReservedWordBox {
    @JS init(in: Int) {}
    @JS func run(for: Int) {}
    @JS static func make(default: Int) -> Int { 0 }
}

Original file line number Diff line number Diff line change
@@ -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" : [

]
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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;
}>;
Loading
Loading