From 5f288bde87535146a87685d35c74235e5b320c5f Mon Sep 17 00:00:00 2001 From: Michael Schmidt Date: Sun, 8 Dec 2024 20:24:36 +0100 Subject: [PATCH] Fix handling of JS keyword when causing invalid code gen (#4329) --- CHANGELOG.md | 12 ++ crates/cli/tests/reference/import.js | 21 ++ crates/cli/tests/reference/import.rs | 26 ++- crates/cli/tests/reference/keyword.d.ts | 6 + crates/cli/tests/reference/keyword.js | 91 ++++++++ crates/cli/tests/reference/keyword.rs | 57 +++++ crates/cli/tests/reference/keyword.wat | 20 ++ crates/macro-support/src/parser.rs | 228 +++++++++++++++----- crates/macro/ui-tests/import-keyword.rs | 68 ++++++ crates/macro/ui-tests/import-keyword.stderr | 59 +++++ 10 files changed, 536 insertions(+), 52 deletions(-) create mode 100644 crates/cli/tests/reference/keyword.d.ts create mode 100644 crates/cli/tests/reference/keyword.js create mode 100644 crates/cli/tests/reference/keyword.rs create mode 100644 crates/cli/tests/reference/keyword.wat create mode 100644 crates/macro/ui-tests/import-keyword.rs create mode 100644 crates/macro/ui-tests/import-keyword.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index afdbc73f5eb..17a86bbfdbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,18 @@ * Optional parameters are now typed as `T | undefined | null` to reflect the actual JS behavior. [#4188](https://github.com/rustwasm/wasm-bindgen/pull/4188) +### Fixed + +- Fixed using [JavaScript keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#keywords) as identifiers not being handled correctly. + [#4329](https://github.com/rustwasm/wasm-bindgen/pull/4329) + + - Using JS keywords as `struct` and `enum` names will now error at compile time, instead of causing invalid JS code gen. + - Using JS keywords that are not valid to call or access properties on will now error at compile time, instead of causing invalid JS code gen if used as: + 1. The first part of a `js_namespace` on imports. + 2. The name of an imported type or constant if the type or constant does not have a `js_namespace` or `module` attribute. + 3. The name of an imported function if the function is not a method and does not have a `js_namespace` or `module` attribute. + - Using JS keywords on imports in places other than the above will no longer cause the keywords to be escaped as `_{keyword}`. + -------------------------------------------------------------------------------- ## [0.2.99](https://github.com/rustwasm/wasm-bindgen/compare/0.2.98...0.2.99) diff --git a/crates/cli/tests/reference/import.js b/crates/cli/tests/reference/import.js index 321ae6901fd..5fd514c7295 100644 --- a/crates/cli/tests/reference/import.js +++ b/crates/cli/tests/reference/import.js @@ -1,3 +1,5 @@ +import { default as default1 } from 'tests/wasm/import_class.js'; + let wasm; export function __wbg_set_wasm(val) { wasm = val; @@ -65,6 +67,20 @@ export function __wbg_catchme_f7d87ea824a61e87() { return handleError(function ( catch_me(); }, arguments) }; +export function __wbg_get_56ba567010fb9959(arg0) { + const ret = arg0.get(); + return ret; +}; + +export function __wbg_myfunction_8c7b624429f78550() { + b.my_function(); +}; + +export function __wbg_new_d21827b66c7fd25d(arg0) { + const ret = new default1(arg0); + return ret; +}; + export function __wbg_nocatch_be850a8dddd9599d() { no_catch(); }; @@ -73,6 +89,11 @@ export function __wbg_reload_84c12f152ad689f0() { window.location.reload(); }; +export function __wbg_static_accessor_CONST_9e9d5ae758197645() { + const ret = a.CONST; + return ret; +}; + export function __wbg_write_c2ce0ce33a6087d5(arg0, arg1) { window.document.write(getStringFromWasm0(arg0, arg1)); }; diff --git a/crates/cli/tests/reference/import.rs b/crates/cli/tests/reference/import.rs index a7bea1953ed..1299f5eaf83 100644 --- a/crates/cli/tests/reference/import.rs +++ b/crates/cli/tests/reference/import.rs @@ -23,12 +23,36 @@ extern "C" { fn add(a: f64, b: f64) -> f64; } +#[wasm_bindgen(js_namespace = ["a"])] +extern "C" { + // test that namespaces are overwritten and not inherited/concatenated + #[wasm_bindgen(js_namespace = ["b"])] + fn my_function(); + #[wasm_bindgen(thread_local_v2)] + static CONST: f64; +} + +#[wasm_bindgen(module = "tests/wasm/import_class.js")] +extern "C" { + #[wasm_bindgen(js_name = default)] + type RenamedTypes; + #[wasm_bindgen(constructor, js_class = default)] + fn new(arg: i32) -> RenamedTypes; + #[wasm_bindgen(method, js_class = default)] + fn get(this: &RenamedTypes) -> i32; +} + #[wasm_bindgen] pub fn exported() -> Result<(), JsValue> { bar_from_foo(); - let _ = add(1.0, 2.0); + let _ = add(CONST.with(Clone::clone), 2.0); reload(); write(""); no_catch(); + my_function(); + + let f = RenamedTypes::new(1); + assert_eq!(f.get(), 2); + catch_me() } diff --git a/crates/cli/tests/reference/keyword.d.ts b/crates/cli/tests/reference/keyword.d.ts new file mode 100644 index 00000000000..4bc758e17b7 --- /dev/null +++ b/crates/cli/tests/reference/keyword.d.ts @@ -0,0 +1,6 @@ +/* tslint:disable */ +/* eslint-disable */ +export function exported(): void; +export function _function(): void; +export function _var(): void; +export function weird_arguments(_new: number, _var: number, _switch: number, _default: number, _arguments: number): void; diff --git a/crates/cli/tests/reference/keyword.js b/crates/cli/tests/reference/keyword.js new file mode 100644 index 00000000000..bfda76214e4 --- /dev/null +++ b/crates/cli/tests/reference/keyword.js @@ -0,0 +1,91 @@ +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} + + +const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; + +let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachedUint8ArrayMemory0 = null; + +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +export function exported() { + wasm.exported(); +} + +export function _function() { + wasm._function(); +} + +export function _var() { + wasm._var(); +} + +/** + * @param {number} _new + * @param {number} _var + * @param {number} _switch + * @param {number} _default + * @param {number} _arguments + */ +export function weird_arguments(_new, _var, _switch, _default, _arguments) { + wasm.weird_arguments(_new, _var, _switch, _default, _arguments); +} + +export function __wbg_await_e0a0e75be8b6fef6() { + await(); +}; + +export function __wbg_let_8d461e9e0592bd8c(arg0) { + arg0.let(); +}; + +export function __wbg_new_4b026aaf1c1e4438() { + const ret = A.new(); + return ret; +}; + +export function __wbg_new_d4bfd9add722b492() { + const ret = window.__TAURI__.menu.Menu.new(); + return ret; +}; + +export function __wbg_new_e17dd7c5a1cd57d8() { + B.new(); +}; + +export function __wbg_static_accessor_TRUE_c6b68bf8545d99a3() { + const ret = true; + return ret; +}; + +export function __wbindgen_init_externref_table() { + const table = wasm.__wbindgen_export_0; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + ; +}; + +export function __wbindgen_throw(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; + diff --git a/crates/cli/tests/reference/keyword.rs b/crates/cli/tests/reference/keyword.rs new file mode 100644 index 00000000000..ee1393b6ff2 --- /dev/null +++ b/crates/cli/tests/reference/keyword.rs @@ -0,0 +1,57 @@ +use wasm_bindgen::prelude::*; + +// Imports with keywords + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen] + pub type A; + + #[wasm_bindgen(static_method_of = A, js_name = "new")] + pub fn static_new() -> A; + #[wasm_bindgen(js_namespace = ["B"], js_name = "new")] + pub fn namespace_new(); + + #[wasm_bindgen(method, js_name = "let")] + pub fn keyword_let(ptr: &A); + + // await is not a reserved keyword in JS + pub fn r#await(); + + // true & false are reserved keywords in JS, but we allow them anyway + #[wasm_bindgen(thread_local_v2, js_name = "true")] + static TRUE: JsValue; +} + +// https://github.com/rustwasm/wasm-bindgen/issues/4317 +#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "menu"])] +extern "C" { + #[wasm_bindgen] + pub type Menu; + + #[wasm_bindgen(static_method_of = Menu)] + pub fn new() -> Menu; +} + +// This function ensures the imported stuff isn't optimized out +#[wasm_bindgen] +pub fn exported() { + let a = A::static_new(); + let _ = a.keyword_let(); + let _ = namespace_new(); + let _ = r#await(); + std::hint::black_box(&TRUE); + + let _ = Menu::new(); +} + +// Exports with keywords that we allow and are renamed automatically. + +#[wasm_bindgen] +pub fn function() {} + +#[wasm_bindgen(js_name = "var")] +pub fn sane_name() {} + +#[wasm_bindgen] +pub fn weird_arguments(new: u32, var: u32, r#switch: u32, default: u32, arguments: u32) {} diff --git a/crates/cli/tests/reference/keyword.wat b/crates/cli/tests/reference/keyword.wat new file mode 100644 index 00000000000..41921ddfb93 --- /dev/null +++ b/crates/cli/tests/reference/keyword.wat @@ -0,0 +1,20 @@ +(module $reference_test.wasm + (type (;0;) (func)) + (type (;1;) (func (param i32 i32 i32 i32 i32))) + (import "./reference_test_bg.js" "__wbindgen_init_externref_table" (func (;0;) (type 0))) + (func $weird_arguments (;1;) (type 1) (param i32 i32 i32 i32 i32)) + (func $exported (;2;) (type 0)) + (func $_function (;3;) (type 0)) + (func $_var (;4;) (type 0)) + (table (;0;) 128 externref) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "exported" (func $exported)) + (export "_function" (func $_function)) + (export "_var" (func $_var)) + (export "weird_arguments" (func $weird_arguments)) + (export "__wbindgen_export_0" (table 0)) + (export "__wbindgen_start" (func 0)) + (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext") +) + diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 9d7ad85a00c..46c76a1ef21 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -19,29 +19,90 @@ use crate::ClassMarker; thread_local!(static ATTRS: AttributeParseState = Default::default()); -/// Javascript keywords which are not keywords in Rust. -const JS_KEYWORDS: [&str; 20] = [ - "class", +/// Javascript keywords. +/// +/// Note that some of these keywords are only reserved in strict mode. Since we +/// generate strict mode JS code, we treat all of these as reserved. +/// +/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words +const JS_KEYWORDS: [&str; 47] = [ + "arguments", + "break", "case", "catch", + "class", + "const", + "continue", "debugger", "default", "delete", + "do", + "else", + "enum", + "eval", "export", "extends", + "false", "finally", + "for", "function", + "if", + "implements", "import", + "in", "instanceof", + "interface", + "let", "new", "null", + "package", + "private", + "protected", + "public", + "return", + "static", + "super", "switch", "this", "throw", + "true", + "try", + "typeof", "var", "void", + "while", "with", + "yield", ]; + +/// Javascript keywords that behave like values in that they can be called like +/// functions or have properties accessed on them. +/// +/// Naturally, this list is a subset of `JS_KEYWORDS`. +const VALUE_LIKE_JS_KEYWORDS: [&str; 7] = [ + "eval", // eval is a function-like keyword, so e.g. `eval(...)` is valid + "false", // false resolves to a boolean value, so e.g. `false.toString()` is valid + "import", // import.meta and import() + "new", // new.target + "super", // super can be used for a function call (`super(...)`) or property lookup (`super.prop`) + "this", // this obviously can be used as a value + "true", // true resolves to a boolean value, so e.g. `false.toString()` is valid +]; + +/// Returns whether the given string is a JS keyword. +fn is_js_keyword(keyword: &str) -> bool { + JS_KEYWORDS.contains(&keyword) +} +/// Returns whether the given string is a JS keyword that does NOT behave like +/// a value. +/// +/// Value-like keywords can be called like functions or have properties +/// accessed, which makes it possible to use them in imports. In general, +/// imports should use this function to check for reserved keywords. +fn is_non_value_js_keyword(keyword: &str) -> bool { + JS_KEYWORDS.contains(&keyword) && !VALUE_LIKE_JS_KEYWORDS.contains(&keyword) +} + #[derive(Default)] struct AttributeParseState { parsed: Cell, @@ -56,6 +117,14 @@ pub struct BindgenAttrs { pub attrs: Vec<(Cell, BindgenAttr)>, } +/// A list of identifiers representing the namespace prefix of an imported +/// function or constant. +/// +/// The list is guaranteed to be non-empty and not start with a JS keyword. +#[cfg_attr(feature = "extra-traits", derive(Debug))] +#[derive(Clone)] +pub struct JsNamespace(Vec); + macro_rules! attrgen { ($mac:ident) => { $mac! { @@ -63,7 +132,7 @@ macro_rules! attrgen { (constructor, Constructor(Span)), (method, Method(Span)), (static_method_of, StaticMethodOf(Span, Ident)), - (js_namespace, JsNamespace(Span, Vec, Vec)), + (js_namespace, JsNamespace(Span, JsNamespace, Vec)), (module, Module(Span, String, Span)), (raw_module, RawModule(Span, String, Span)), (inline_js, InlineJs(Span, String, Span)), @@ -160,14 +229,14 @@ macro_rules! methods { } }; - (@method $name:ident, $variant:ident(Span, Vec, Vec)) => { - fn $name(&self) -> Option<(&[String], &[Span])> { + (@method $name:ident, $variant:ident(Span, JsNamespace, Vec)) => { + fn $name(&self) -> Option<(JsNamespace, &[Span])> { self.attrs .iter() .find_map(|a| match &a.1 { BindgenAttr::$variant(_, ss, spans) => { a.0.set(true); - Some((&ss[..], &spans[..])) + Some((ss.clone(), &spans[..])) } _ => None, }) @@ -358,7 +427,7 @@ impl Parse for BindgenAttr { return Ok(BindgenAttr::$variant(attr_span, val, span)) }); - (@parser $variant:ident(Span, Vec, Vec)) => ({ + (@parser $variant:ident(Span, JsNamespace, Vec)) => ({ input.parse::()?; let (vals, spans) = match input.parse::() { Ok(exprs) => { @@ -377,6 +446,10 @@ impl Parse for BindgenAttr { } } + if vals.is_empty() { + return Err(syn::Error::new(exprs.span(), "Empty namespace lists are not allowed.")); + } + (vals, spans) }, Err(_) => { @@ -384,7 +457,14 @@ impl Parse for BindgenAttr { (vec![ident.to_string()], vec![ident.span()]) } }; - return Ok(BindgenAttr::$variant(attr_span, vals, spans)) + + let first = &vals[0]; + if is_non_value_js_keyword(first) { + let msg = format!("Namespace cannot start with the JS keyword `{}`", first); + return Err(syn::Error::new(spans[0], msg)); + } + + return Ok(BindgenAttr::$variant(attr_span, JsNamespace(vals), spans)) }); } @@ -441,6 +521,14 @@ impl ConvertToAst<(&ast::Program, BindgenAttrs)> for &mut syn::ItemStruct { .js_name() .map(|s| s.0.to_string()) .unwrap_or(self.ident.unraw().to_string()); + if is_js_keyword(&js_name) { + bail_span!( + self.ident, + "struct cannot use the JS keyword `{}` as its name", + js_name + ); + } + let is_inspectable = attrs.inspectable().is_some(); let getter_with_clone = attrs.getter_with_clone(); for (i, field) in self.fields.iter_mut().enumerate() { @@ -524,16 +612,14 @@ impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option), ) -> Result { - let mut wasm = function_from_decl( + let (mut wasm, _) = function_from_decl( &self.sig.ident, &opts, self.sig.clone(), self.attrs.clone(), self.vis.clone(), FunctionPosition::Extern, - Some(&["default"]), - )? - .0; + )?; let catch = opts.catch().is_some(); let variadic = opts.variadic().is_some(); let js_ret = if catch { @@ -727,7 +813,7 @@ impl ConvertToAst<(&ast::Program, BindgenAttrs)> for syn::ForeignItemType { let shim = format!( "__wbg_instanceof_{}_{}", self.ident, - ShortHash((attrs.js_namespace().map(|(ns, _)| ns), &self.ident)) + ShortHash((attrs.js_namespace().map(|(ns, _)| ns.0), &self.ident)) ); let mut extends = Vec::new(); let mut vendor_prefixes = Vec::new(); @@ -886,25 +972,24 @@ impl ConvertToAst for syn::ItemFn { ); } - let ret = function_from_decl( + let (mut ret, _) = function_from_decl( &self.sig.ident, &attrs, self.sig.clone(), self.attrs, self.vis, FunctionPosition::Free, - Some(&["default"]), )?; attrs.check_used(); - Ok(ret.0) - } -} -pub(crate) fn is_js_keyword(keyword: &str, skip: Option<&[&str]>) -> bool { - JS_KEYWORDS - .iter() - .filter(|keyword| skip.filter(|skip| skip.contains(keyword)).is_none()) - .any(|this| *this == keyword) + // Due to legacy behavior, we need to escape all keyword identifiers as + // `_keyword`, except `default` + if is_js_keyword(&ret.name) && ret.name != "default" { + ret.name = format!("_{}", ret.name); + } + + Ok(ret) + } } /// Returns whether `self` is passed by reference or by value. @@ -942,7 +1027,6 @@ fn function_from_decl( attrs: Vec, vis: syn::Visibility, position: FunctionPosition, - skip_keywords: Option<&[&str]>, ) -> Result<(ast::Function, Option), Diagnostic> { if sig.variadic.is_some() { bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions"); @@ -987,8 +1071,11 @@ fn function_from_decl( // E.g. this will replace `fn foo(class: u32)` to `fn foo(_class: u32)` let replace_colliding_arg = |i: &mut syn::PatType| { if let syn::Pat::Ident(ref mut i) = *i.pat { - let ident = i.ident.to_string(); - if is_js_keyword(ident.as_str(), skip_keywords) { + let ident = i.ident.unraw().to_string(); + // JS keywords are NEVER allowed as argument names. Since argument + // names are considered an implementation detail in JS, we can + // safely rename them to avoid collisions. + if is_js_keyword(&ident) { i.ident = Ident::new(format!("_{}", ident).as_str(), i.ident.span()); } } @@ -1047,26 +1134,11 @@ fn function_from_decl( OperationKind::Setter(_) => "set_", _ => "", }; - let name = if prefix.is_empty() - && opts.method().is_none() - && is_js_keyword(js_name, skip_keywords) - { - format!("_{}", js_name) - } else { - format!("{}{}", prefix, js_name) - }; - (name, js_name_span, true) + (format!("{}{}", prefix, js_name), js_name_span, true) } else { - let name = if !matches!(position, FunctionPosition::Impl { .. }) - && opts.method().is_none() - && is_js_keyword(&decl_name.to_string(), skip_keywords) - { - format!("_{}", decl_name.unraw()) - } else { - decl_name.unraw().to_string() - }; - (name, decl_name.span(), false) + (decl_name.unraw().to_string(), decl_name.span(), false) }; + Ok(( ast::Function { arguments, @@ -1344,7 +1416,6 @@ impl MacroParse<&ClassMarker> for &mut syn::ImplItemFn { self.attrs.clone(), self.vis.clone(), FunctionPosition::Impl { self_ty: class }, - None, )?; let method_kind = if opts.constructor().is_some() { ast::MethodKind::Constructor @@ -1481,11 +1552,18 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum { } let generate_typescript = opts.skip_typescript().is_none(); + let comments = extract_doc_comments(&self.attrs); let js_name = opts .js_name() .map(|s| s.0) .map_or_else(|| self.ident.to_string(), |s| s.to_string()); - let comments = extract_doc_comments(&self.attrs); + if is_js_keyword(&js_name) { + bail_span!( + self.ident, + "enum cannot use the JS keyword `{}` as its name", + js_name + ); + } opts.check_used(); @@ -1653,7 +1731,7 @@ impl MacroParse for syn::ItemForeignMod { "only foreign mods with the `C` ABI are allowed" )); } - let js_namespace = opts.js_namespace().map(|(s, _)| s.to_owned()); + let js_namespace = opts.js_namespace().map(|(s, _)| s); let module = module_from_opts(program, &opts) .map_err(|e| errors.push(e)) .unwrap_or_default(); @@ -1674,7 +1752,7 @@ impl MacroParse for syn::ItemForeignMod { struct ForeignItemCtx { module: Option, - js_namespace: Option>, + js_namespace: Option, } impl MacroParse for syn::ForeignItem { @@ -1709,8 +1787,9 @@ impl MacroParse for syn::ForeignItem { let js_namespace = item_opts .js_namespace() - .map(|(s, _)| s.to_owned()) - .or(ctx.js_namespace); + .map(|(s, _)| s) + .or(ctx.js_namespace) + .map(|s| s.0); let module = ctx.module; let kind = match self { @@ -1720,6 +1799,53 @@ impl MacroParse for syn::ForeignItem { _ => panic!("only foreign functions/types allowed for now"), }; + // check for JS keywords + + // We only need to check if there isn't a JS namespace or module. If + // there is namespace, then we already checked the namespace while + // parsing. If there is a module, we can rename the import symbol to + // avoid using keywords. + let needs_check = js_namespace.is_none() && module.is_none(); + if needs_check { + match &kind { + ast::ImportKind::Function(import_function) => { + if matches!(import_function.kind, ast::ImportFunctionKind::Normal) + && is_non_value_js_keyword(&import_function.function.name) + { + bail_span!( + import_function.rust_name, + "Imported function cannot use the JS keyword `{}` as its name.", + import_function.function.name + ); + } + } + ast::ImportKind::Static(import_static) => { + if is_non_value_js_keyword(&import_static.js_name) { + bail_span!( + import_static.rust_name, + "Imported static cannot use the JS keyword `{}` as its name.", + import_static.js_name + ); + } + } + ast::ImportKind::String(_) => { + // static strings don't have JS names, so we don't need to check for JS keywords + } + ast::ImportKind::Type(import_type) => { + if is_non_value_js_keyword(&import_type.js_name) { + bail_span!( + import_type.rust_name, + "Imported type cannot use the JS keyword `{}` as its name.", + import_type.js_name + ); + } + } + ast::ImportKind::Enum(_) => { + // string enums aren't possible here + } + } + } + program.imports.push(ast::Import { module, js_namespace, diff --git a/crates/macro/ui-tests/import-keyword.rs b/crates/macro/ui-tests/import-keyword.rs new file mode 100644 index 00000000000..3c44a45c84d --- /dev/null +++ b/crates/macro/ui-tests/import-keyword.rs @@ -0,0 +1,68 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + type A; + + // directly import function with reserved keywords + #[wasm_bindgen] + fn function(); + // directly import function with reserved keywords + #[wasm_bindgen(js_name = "var")] + fn keyword_var(); + + // this is fine, because it's a method + #[wasm_bindgen(method, js_name = "let")] + fn keyword_let(arg: &A); + // this is fine, because it's static + #[wasm_bindgen(static_method_of = A, js_name = "let")] + fn static_keyword_let(); + + // directly import static with reserved keywords + #[wasm_bindgen(thread_local_v2, js_name = "const")] + static CONST: u32; + + // directly import type with reserved keywords + #[wasm_bindgen(js_name = "throw")] + type B; + // fine with a namespace + #[wasm_bindgen(js_name = "throw", js_namespace = ["foo"])] + type C; +} + +// Namespaces + +// namespace on extern block +#[wasm_bindgen(js_namespace = ["public", "foo"])] +extern "C" {} + +#[wasm_bindgen] +extern "C" { + // invalid, because of its namespace + #[wasm_bindgen(js_namespace = ["const", "bar"])] + fn function(); + // okay, because it defines its own namespace + #[wasm_bindgen(thread_local_v2, js_name = "const", js_namespace = ["bar", "new"])] + static CONST: u32; +} + +#[wasm_bindgen] +extern "C" { + // empty namespace to be funny + #[wasm_bindgen(js_namespace = [])] + fn function(); +} + +// Classes and enums + +#[wasm_bindgen] +pub struct class; +#[wasm_bindgen] +pub struct r#true; // forbid value-like keywords +#[wasm_bindgen] +pub enum switch { + A, + B, +} + +fn main() {} diff --git a/crates/macro/ui-tests/import-keyword.stderr b/crates/macro/ui-tests/import-keyword.stderr new file mode 100644 index 00000000000..ecc9f087ded --- /dev/null +++ b/crates/macro/ui-tests/import-keyword.stderr @@ -0,0 +1,59 @@ +error: Imported function cannot use the JS keyword `function` as its name. + --> ui-tests/import-keyword.rs:9:8 + | +9 | fn function(); + | ^^^^^^^^ + +error: Imported function cannot use the JS keyword `var` as its name. + --> ui-tests/import-keyword.rs:12:8 + | +12 | fn keyword_var(); + | ^^^^^^^^^^^ + +error: Imported static cannot use the JS keyword `const` as its name. + --> ui-tests/import-keyword.rs:23:12 + | +23 | static CONST: u32; + | ^^^^^ + +error: Imported type cannot use the JS keyword `throw` as its name. + --> ui-tests/import-keyword.rs:27:10 + | +27 | type B; + | ^ + +error: Namespace cannot start with the JS keyword `public` + --> ui-tests/import-keyword.rs:36:32 + | +36 | #[wasm_bindgen(js_namespace = ["public", "foo"])] + | ^^^^^^^^ + +error: Namespace cannot start with the JS keyword `const` + --> ui-tests/import-keyword.rs:42:36 + | +42 | #[wasm_bindgen(js_namespace = ["const", "bar"])] + | ^^^^^^^ + +error: Empty namespace lists are not allowed. + --> ui-tests/import-keyword.rs:52:35 + | +52 | #[wasm_bindgen(js_namespace = [])] + | ^^ + +error: struct cannot use the JS keyword `class` as its name + --> ui-tests/import-keyword.rs:59:12 + | +59 | pub struct class; + | ^^^^^ + +error: struct cannot use the JS keyword `true` as its name + --> ui-tests/import-keyword.rs:61:12 + | +61 | pub struct r#true; // forbid value-like keywords + | ^^^^^^ + +error: enum cannot use the JS keyword `switch` as its name + --> ui-tests/import-keyword.rs:63:10 + | +63 | pub enum switch { + | ^^^^^^