From 26c1dab446a642a8794c7efd7faf2b2a0d01742d Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Fri, 27 Dec 2024 21:32:34 +0000 Subject: [PATCH 01/50] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8e07bc0952..d59ce5a9820 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ ## Install `wasm-bindgen-cli` -You can install it using `cargo install`: +You can install it using `cargo install`: ``` cargo install wasm-bindgen-cli From 55a74d6c1b01cc0cb94a361005a69397cc92a882 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 6 Jan 2025 03:12:12 +0000 Subject: [PATCH 02/50] init --- README.md | 2 +- crates/backend/src/ast.rs | 26 ++ crates/backend/src/encode.rs | 25 ++ crates/cli-support/src/js/binding.rs | 223 ++++++++++++++---- crates/cli-support/src/js/mod.rs | 14 +- crates/cli-support/src/wit/mod.rs | 18 ++ crates/cli-support/src/wit/nonstandard.rs | 20 ++ .../tests/reference/function-attrs-async.d.ts | 34 +++ .../tests/reference/function-attrs-async.rs | 70 ++++++ .../cli/tests/reference/function-attrs.d.ts | 34 +++ crates/cli/tests/reference/function-attrs.js | 170 +++++++++++++ crates/cli/tests/reference/function-attrs.rs | 70 ++++++ crates/cli/tests/reference/function-attrs.wat | 23 ++ crates/macro-support/src/parser.rs | 114 ++++++++- .../ui-tests/optional-function-arg-attr.rs | 14 ++ .../optional-function-arg-attr.stderr | 5 + crates/macro/ui-tests/unused-attributes.rs | 20 ++ .../macro/ui-tests/unused-attributes.stderr | 48 ++++ crates/shared/src/lib.rs | 12 + crates/typescript-tests/src/function_attrs.rs | 70 ++++++ crates/typescript-tests/src/function_attrs.ts | 37 +++ crates/typescript-tests/src/lib.rs | 1 + .../on-rust-exports/function-attributes.md | 139 +++++++++++ 23 files changed, 1144 insertions(+), 45 deletions(-) create mode 100644 crates/cli/tests/reference/function-attrs-async.d.ts create mode 100644 crates/cli/tests/reference/function-attrs-async.rs create mode 100644 crates/cli/tests/reference/function-attrs.d.ts create mode 100644 crates/cli/tests/reference/function-attrs.js create mode 100644 crates/cli/tests/reference/function-attrs.rs create mode 100644 crates/cli/tests/reference/function-attrs.wat create mode 100644 crates/macro/ui-tests/optional-function-arg-attr.rs create mode 100644 crates/macro/ui-tests/optional-function-arg-attr.stderr create mode 100644 crates/typescript-tests/src/function_attrs.rs create mode 100644 crates/typescript-tests/src/function_attrs.ts create mode 100644 guide/src/reference/attributes/on-rust-exports/function-attributes.md diff --git a/README.md b/README.md index d59ce5a9820..d8e07bc0952 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ ## Install `wasm-bindgen-cli` -You can install it using `cargo install`: +You can install it using `cargo install`: ``` cargo install wasm-bindgen-cli diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 0507f055306..34c5c760333 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -392,6 +392,32 @@ pub struct Function { pub generate_jsdoc: bool, /// Whether this is a function with a variadict parameter pub variadic: bool, + /// Function attributes used to provide extra information about function's components + pub fn_attrs: Option, +} + +/// Extra information about a function's components +#[cfg_attr(feature = "extra-traits", derive(Debug))] +#[derive(Clone, Default)] +pub struct FunctionAttributes { + /// Function's return attributes + pub ret: FunctionComponentAttributes, + /// Function's arguments attributes + pub args: Vec, +} + +/// Information about a function's component +#[cfg_attr(feature = "extra-traits", derive(Debug))] +#[derive(Clone, Default)] +pub struct FunctionComponentAttributes { + /// Specifies the type for a function component + pub ty: Option, + /// Description of the function component + pub desc: Option, + /// Specifies a name of the function argument + pub name: Option, + /// Specifies if the component is optional (used for function arguments) + pub optional: bool, } /// Information about a Struct being exported diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index d98fe628450..a443a72cc29 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -220,12 +220,36 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi .iter() .enumerate() .map(|(idx, arg)| { + // use argument's "js_name" if it was provided in attributes + if let Some(arg_js_name) = func + .fn_attrs + .as_ref() + .and_then(|attrs| attrs.args.get(idx).and_then(|v| v.name.clone())) + { + return arg_js_name; + } if let syn::Pat::Ident(x) = &*arg.pat { return x.ident.unraw().to_string(); } format!("arg{}", idx) }) .collect::>(); + let fn_attrs = func.fn_attrs.as_ref().map(|attrs| FunctionAttributes { + ret: FunctionComponentAttributes { + ty: attrs.ret.ty.as_deref(), + desc: attrs.ret.desc.as_deref(), + optional: false, + }, + args: attrs + .args + .iter() + .map(|arg_attr| FunctionComponentAttributes { + ty: arg_attr.ty.as_deref(), + desc: arg_attr.desc.as_deref(), + optional: arg_attr.optional, + }) + .collect::>(), + }); Function { arg_names, asyncness: func.r#async, @@ -233,6 +257,7 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi generate_typescript: func.generate_typescript, generate_jsdoc: func.generate_jsdoc, variadic: func.variadic, + fn_attrs, } } diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 637c3a5edf7..ed52eef5bcf 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -5,8 +5,8 @@ //! generated by `wasm-bindgen` run through this type. use crate::js::Context; -use crate::wit::InstructionData; use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction}; +use crate::wit::{AuxExportFnAttrs, InstructionData}; use anyhow::{anyhow, bail, Error}; use std::collections::HashSet; use std::fmt::Write; @@ -70,6 +70,7 @@ pub struct JsFunction { pub code: String, pub ts_sig: String, pub js_doc: String, + pub ts_doc: String, pub ts_arg_tys: Vec, pub ts_ret_ty: Option, pub ts_refs: HashSet, @@ -125,6 +126,7 @@ impl<'a, 'b> Builder<'a, 'b> { variadic: bool, generate_jsdoc: bool, debug_name: &str, + export_fn_attrs: &Option, ) -> Result { if self .cx @@ -272,17 +274,41 @@ impl<'a, 'b> Builder<'a, 'b> { &mut might_be_optional_field, asyncness, variadic, + export_fn_attrs, ); let js_doc = if generate_jsdoc { - self.js_doc_comments(&function_args, &arg_tys, &ts_ret_ty, variadic) + self.js_ts_doc_comments( + &function_args, + &arg_tys, + &ts_ret_ty, + variadic, + export_fn_attrs, + false, + ) } else { String::new() }; + // generate ts_doc + // ts doc is slightly different than js doc, where there is no + // arguments types followed after @param tag, as well as no special + // casings for arguments names such as "@param {string} [arg]" that + // tags the argument as optional, for ts doc we only need arg names + // and rest are just derived from function ts signature + let ts_doc = self.js_ts_doc_comments( + &function_args, + &arg_tys, + &ts_ret_ty, + variadic, + export_fn_attrs, + true, + ); + Ok(JsFunction { code, ts_sig, js_doc, + ts_doc, ts_arg_tys, ts_ret_ty, ts_refs, @@ -305,31 +331,63 @@ impl<'a, 'b> Builder<'a, 'b> { might_be_optional_field: &mut bool, asyncness: bool, variadic: bool, + export_fn_attrs: &Option, ) -> (String, Vec, Option, HashSet) { + // flatten args types overrides + let args_overrides = export_fn_attrs + .as_ref() + .map(|v| { + v.args + .iter() + .map(|e| (e.ty.as_ref(), e.optional)) + .collect::>() + }) + .unwrap_or(vec![(None, false); arg_names.len()]); + // Build up the typescript signature as well let mut omittable = true; let mut ts_args = Vec::new(); let mut ts_arg_tys = Vec::new(); let mut ts_refs = HashSet::new(); - for (name, ty) in arg_names.iter().zip(arg_tys).rev() { + for ((name, ty), (ty_override, optional)) in + arg_names.iter().zip(arg_tys).zip(args_overrides).rev() + { // In TypeScript, we can mark optional parameters as omittable // using the `?` suffix, but only if they're not followed by // non-omittable parameters. Therefore iterate the parameter list // in reverse and stop using the `?` suffix for optional params as // soon as a non-optional parameter is encountered. + // + // "optional" attr already enforces this rule for all override + // attrs so we dont need to do the omittable check for args tagged + // with "optional" attr let mut arg = name.to_string(); let mut ts = String::new(); - match ty { - AdapterType::Option(ty) if omittable => { - // e.g. `foo?: string | null` + if let Some(v) = ty_override { + if optional { arg.push_str("?: "); - adapter2ts(ty, TypePosition::Argument, &mut ts, Some(&mut ts_refs)); - ts.push_str(" | null"); - } - ty => { + } else { omittable = false; arg.push_str(": "); - adapter2ts(ty, TypePosition::Argument, &mut ts, Some(&mut ts_refs)); + } + ts.push_str(v); + } else { + match ty { + AdapterType::Option(ty) if omittable => { + // e.g. `foo?: string | null` + arg.push_str("?: "); + adapter2ts(ty, TypePosition::Argument, &mut ts, Some(&mut ts_refs)); + ts.push_str(" | null"); + } + ty => { + adapter2ts(ty, TypePosition::Argument, &mut ts, Some(&mut ts_refs)); + if optional { + arg.push_str("?: "); + } else { + omittable = false; + arg.push_str(": "); + } + } } } arg.push_str(&ts); @@ -359,19 +417,24 @@ impl<'a, 'b> Builder<'a, 'b> { } // Constructors have no listed return type in typescript + let ret_ty_override = export_fn_attrs.as_ref().and_then(|v| v.ret.ty.as_ref()); let mut ts_ret = None; if self.constructor.is_none() { ts.push_str(": "); let mut ret = String::new(); - match result_tys.len() { - 0 => ret.push_str("void"), - 1 => adapter2ts( - &result_tys[0], - TypePosition::Return, - &mut ret, - Some(&mut ts_refs), - ), - _ => ret.push_str("[any]"), + if let Some(v) = &ret_ty_override { + ret.push_str(v); + } else { + match result_tys.len() { + 0 => ret.push_str("void"), + 1 => adapter2ts( + &result_tys[0], + TypePosition::Return, + &mut ret, + Some(&mut ts_refs), + ), + _ => ret.push_str("[any]"), + } } if asyncness { ret = format!("Promise<{}>", ret); @@ -382,15 +445,28 @@ impl<'a, 'b> Builder<'a, 'b> { (ts, ts_arg_tys, ts_ret, ts_refs) } - /// Returns a helpful JS doc comment which lists types for all parameters + /// Returns a helpful JS/TS doc comment which lists types for all parameters /// and the return value. - fn js_doc_comments( + fn js_ts_doc_comments( &self, arg_names: &[String], arg_tys: &[&AdapterType], ts_ret: &Option, variadic: bool, + export_fn_attrs: &Option, + is_ts: bool, ) -> String { + // flatten args attributes overrides + let args_overrides = &export_fn_attrs + .as_ref() + .map(|v| { + v.args + .iter() + .map(|e| (e.ty.as_ref(), e.desc.as_ref(), e.optional)) + .collect::>() + }) + .unwrap_or(vec![(None, None, false); arg_names.len()]); + let (variadic_arg, fn_arg_names) = match arg_names.split_last() { Some((last, args)) if variadic => (Some(last), args), _ => (None, arg_names), @@ -399,23 +475,54 @@ impl<'a, 'b> Builder<'a, 'b> { let mut omittable = true; let mut js_doc_args = Vec::new(); - for (name, ty) in fn_arg_names.iter().zip(arg_tys).rev() { - let mut arg = "@param {".to_string(); - - match ty { - AdapterType::Option(ty) if omittable => { - adapter2ts(ty, TypePosition::Argument, &mut arg, None); - arg.push_str(" | null} "); + for ((name, ty), (ty_override, desc, optional)) in + fn_arg_names.iter().zip(arg_tys).zip(args_overrides).rev() + { + let mut arg = "@param ".to_string(); + if is_ts { + // we dont need arg type for ts doc, only arg name + arg.push_str(name); + } else if let Some(v) = ty_override { + arg.push('{'); + arg.push_str(v); + arg.push_str("} "); + if *optional { arg.push('['); arg.push_str(name); arg.push(']'); - } - _ => { + } else { omittable = false; - adapter2ts(ty, TypePosition::Argument, &mut arg, None); - arg.push_str("} "); arg.push_str(name); } + } else { + match ty { + AdapterType::Option(ty) if omittable => { + arg.push('{'); + adapter2ts(ty, TypePosition::Argument, &mut arg, None); + arg.push_str(" | null} "); + arg.push('['); + arg.push_str(name); + arg.push(']'); + } + _ => { + arg.push('{'); + adapter2ts(ty, TypePosition::Argument, &mut arg, None); + if *optional { + arg.push_str("} "); + arg.push('['); + arg.push_str(name); + arg.push(']'); + } else { + omittable = false; + arg.push_str("} "); + arg.push_str(name); + } + } + } + } + if let Some(v) = desc { + arg.push_str(" - "); + arg.push_str(v); } arg.push('\n'); js_doc_args.push(arg); @@ -423,16 +530,50 @@ impl<'a, 'b> Builder<'a, 'b> { let mut ret: String = js_doc_args.into_iter().rev().collect(); - if let (Some(name), Some(ty)) = (variadic_arg, arg_tys.last()) { - ret.push_str("@param {..."); - adapter2ts(ty, TypePosition::Argument, &mut ret, None); - ret.push_str("} "); - ret.push_str(name); + if let (Some(name), Some(ty), Some((ty_override, desc, _))) = + (variadic_arg, arg_tys.last(), args_overrides.last()) + { + ret.push_str("@param "); + if is_ts { + // we dont need arg type for ts doc, so only include arg name + ret.push_str(name); + } else { + ret.push_str("{..."); + if let Some(v) = ty_override { + ret.push_str(v); + } else { + adapter2ts(ty, TypePosition::Argument, &mut ret, None); + } + ret.push_str("} "); + ret.push_str(name); + } + if let Some(v) = desc { + ret.push_str(" - "); + ret.push_str(v); + } ret.push('\n'); } - if let Some(ts) = ts_ret { - if ts != "void" { - ret.push_str(&format!("@returns {{{}}}", ts)); + let (ret_ty_override, ret_desc) = export_fn_attrs + .as_ref() + .map(|v| (v.ret.ty.as_ref(), v.ret.desc.as_ref())) + .unwrap_or((None, None)); + if let Some(ts) = ret_ty_override.or(ts_ret.as_ref()) { + // skip if type is void and there is no description + if ts != "void" || ret_desc.is_some() { + if is_ts { + // only if there is return description as we dont want empty @return tag + if ret_desc.is_some() { + ret.push_str("@returns"); + } + } else { + ret.push_str(&format!("@returns {{{}}}", ts)); + } + + // append return description + if let Some(v) = ret_desc { + ret.push(' '); + ret.push_str(v); + } } } ret diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index c7252852b49..c578cb4c780 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2833,12 +2833,14 @@ __wbg_set_wasm(wasm);" let mut asyncness = false; let mut variadic = false; let mut generate_jsdoc = false; + let mut export_fn_attrs = &None; match kind { ContextAdapterKind::Export(export) => { arg_names = &export.arg_names; asyncness = export.asyncness; variadic = export.variadic; generate_jsdoc = export.generate_jsdoc; + export_fn_attrs = &export.export_fn_attrs; match &export.kind { AuxExportKind::Function(_) => {} AuxExportKind::Constructor(class) => builder.constructor(class), @@ -2870,6 +2872,7 @@ __wbg_set_wasm(wasm);" ts_ret_ty, ts_refs, js_doc, + ts_doc, code, might_be_optional_field, catch, @@ -2883,6 +2886,7 @@ __wbg_set_wasm(wasm);" variadic, generate_jsdoc, &debug_name, + export_fn_attrs, ) .with_context(|| "failed to generates bindings for ".to_string() + &debug_name)?; @@ -2897,8 +2901,16 @@ __wbg_set_wasm(wasm);" let ts_sig = export.generate_typescript.then_some(ts_sig.as_str()); + // only include ts_doc for format if there was arguments or return var description + // this is because if there are no arguments or return var description, ts_doc + // provides no additional info on top of what ts_sig already does + let ts_doc_opts = export_fn_attrs.as_ref().and_then(|v| { + (v.ret.desc.is_some() || v.args.iter().any(|e| e.desc.is_some())) + .then_some(ts_doc) + }); + let js_docs = format_doc_comments(&export.comments, Some(js_doc)); - let ts_docs = format_doc_comments(&export.comments, None); + let ts_docs = format_doc_comments(&export.comments, ts_doc_opts); match &export.kind { AuxExportKind::Function(name) => { diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 930fddca566..0ec18cd6e5c 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -533,6 +533,22 @@ impl<'a> Context<'a> { generate_typescript: export.function.generate_typescript, generate_jsdoc: export.function.generate_jsdoc, variadic: export.function.variadic, + export_fn_attrs: export.function.fn_attrs.map(|attrs| AuxExportFnAttrs { + ret: AuxExportFnCompAttrs { + ty: attrs.ret.ty.map(String::from), + desc: attrs.ret.desc.map(String::from), + optional: false, + }, + args: attrs + .args + .iter() + .map(|arg_attr| AuxExportFnCompAttrs { + ty: arg_attr.ty.map(String::from), + desc: arg_attr.desc.map(String::from), + optional: arg_attr.optional, + }) + .collect::>(), + }), }, ); Ok(()) @@ -958,6 +974,7 @@ impl<'a> Context<'a> { generate_typescript: field.generate_typescript, generate_jsdoc: field.generate_jsdoc, variadic: false, + export_fn_attrs: None, }, ); @@ -990,6 +1007,7 @@ impl<'a> Context<'a> { generate_typescript: field.generate_typescript, generate_jsdoc: field.generate_jsdoc, variadic: false, + export_fn_attrs: None, }, ); } diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index 1e9579dc8ba..1076538283a 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -86,6 +86,26 @@ pub struct AuxExport { pub generate_jsdoc: bool, /// Whether typescript bindings should be generated for this export. pub variadic: bool, + /// Extra info about an exporting function components attributes + pub export_fn_attrs: Option, +} + +#[derive(Clone, Debug)] +pub struct AuxExportFnAttrs { + /// Exporting function's return attributes + pub ret: AuxExportFnCompAttrs, + /// Exporting function's arguments attributes + pub args: Vec, +} + +#[derive(Clone, Debug)] +pub struct AuxExportFnCompAttrs { + /// Specifies the type for a function component + pub ty: Option, + /// Description of the function component + pub desc: Option, + /// Specifies if the component is optional (used for function arguments) + pub optional: bool, } /// All possible kinds of exports from a Wasm module. diff --git a/crates/cli/tests/reference/function-attrs-async.d.ts b/crates/cli/tests/reference/function-attrs-async.d.ts new file mode 100644 index 00000000000..ca79c04a4e5 --- /dev/null +++ b/crates/cli/tests/reference/function-attrs-async.d.ts @@ -0,0 +1,34 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Description for fn_with_attr + * @param firstArg - some number + * @param secondArg + * @returns returns 1 if arg2 is true, or arg1 if arg2 is undefined or false + */ +export function fn_with_attr(firstArg: number, secondArg?: boolean): Promise; +/** + * Description for HoldsNumber + */ +export class HoldsNumber { + private constructor(); + free(): void; + /** + * Description for static_fn_with_attr + * @param firstArg - some number + * @param secondArg + * @returns returns an instance of HoldsNumber, holding arg1 if arg2 is undefined and holding arg2 if not + */ + static static_fn_with_attr(firstArg: number, secondArg?: number): Promise; + /** + * Description for method_with_attr + * @param firstArg - some number + * @param secondArg + * @returns returns arg1 if arg2 is true, or holding value of self if arg2 is undefined or false + */ + method_with_attr(firstArg: number, secondArg?: boolean): Promise; + /** + * Inner value + */ + readonly inner: number; +} diff --git a/crates/cli/tests/reference/function-attrs-async.rs b/crates/cli/tests/reference/function-attrs-async.rs new file mode 100644 index 00000000000..66b82164234 --- /dev/null +++ b/crates/cli/tests/reference/function-attrs-async.rs @@ -0,0 +1,70 @@ +use wasm_bindgen::prelude::*; + +/// Description for fn_with_attr +#[wasm_bindgen( + return_type = "number", + return_description = "returns 1 if arg2 is true, or arg1 if arg2 is undefined or false" +)] +pub async fn fn_with_attr( + #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, + #[wasm_bindgen(js_name = "secondArg", param_type = "boolean", optional)] arg2: JsValue, +) -> Result { + if arg2.is_undefined() { + Ok(arg1.into()) + } else if arg2.is_truthy() { + Ok(1u32.into()) + } else { + Ok(arg1.into()) + } +} + +/// Description for HoldsNumber +#[wasm_bindgen] +pub struct HoldsNumber { + inner: JsValue, +} + +#[wasm_bindgen] +impl HoldsNumber { + /// Inner value + #[wasm_bindgen(getter = "inner", return_type = "number")] + pub fn get_inner(&self) -> JsValue { + self.inner.clone() + } + + /// Description for static_fn_with_attr + #[wasm_bindgen( + return_description = "returns an instance of HoldsNumber, holding arg1 if arg2 is undefined and holding arg2 if not" + )] + pub async fn static_fn_with_attr( + #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, + #[wasm_bindgen(js_name = "secondArg", param_type = "number")] + #[wasm_bindgen(optional)] + arg2: JsValue, + ) -> Result { + if arg2.is_undefined() { + Ok(HoldsNumber { inner: arg1.into() }) + } else { + Ok(HoldsNumber { inner: arg2 }) + } + } + + /// Description for method_with_attr + #[wasm_bindgen( + return_type = "number", + return_description = "returns arg1 if arg2 is true, or holding value of self if arg2 is undefined or false" + )] + pub async fn method_with_attr( + &self, + #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, + #[wasm_bindgen(js_name = "secondArg", param_type = "boolean", optional)] arg2: JsValue, + ) -> Result { + if arg2.is_undefined() { + Ok(self.inner.clone()) + } else if arg2.is_truthy() { + Ok(arg1.into()) + } else { + Ok(self.inner.clone()) + } + } +} diff --git a/crates/cli/tests/reference/function-attrs.d.ts b/crates/cli/tests/reference/function-attrs.d.ts new file mode 100644 index 00000000000..13ae4389479 --- /dev/null +++ b/crates/cli/tests/reference/function-attrs.d.ts @@ -0,0 +1,34 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Description for fn_with_attr + * @param firstArg - some number + * @param secondArg + * @returns returns 1 if arg2 is true, or arg1 if arg2 is undefined or false + */ +export function fn_with_attr(firstArg: number, secondArg?: boolean): number; +/** + * Description for HoldsNumber + */ +export class HoldsNumber { + private constructor(); + free(): void; + /** + * Description for static_fn_with_attr + * @param firstArg - some number + * @param secondArg + * @returns returns an instance of HoldsNumber, holding arg1 if arg2 is undefined and holding arg2 if not + */ + static static_fn_with_attr(firstArg: number, secondArg?: number): HoldsNumber; + /** + * Description for method_with_attr + * @param firstArg - some number + * @param secondArg + * @returns returns arg1 if arg2 is true, or holding value of self if arg2 is undefined or false + */ + method_with_attr(firstArg: number, secondArg?: boolean): number; + /** + * Inner value + */ + readonly inner: number; +} diff --git a/crates/cli/tests/reference/function-attrs.js b/crates/cli/tests/reference/function-attrs.js new file mode 100644 index 00000000000..e90973bf2a4 --- /dev/null +++ b/crates/cli/tests/reference/function-attrs.js @@ -0,0 +1,170 @@ +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} + + +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +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)); +} + +let cachedDataViewMemory0 = null; + +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} +/** + * Description for fn_with_attr + * @param {number} firstArg - some number + * @param {boolean} [secondArg] + * @returns {number} returns 1 if arg2 is true, or arg1 if arg2 is undefined or false + */ +export function fn_with_attr(firstArg, secondArg) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.fn_with_attr(retptr, firstArg, addHeapObject(secondArg)); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +const HoldsNumberFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_holdsnumber_free(ptr >>> 0, 1)); +/** + * Description for HoldsNumber + */ +export class HoldsNumber { + + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(HoldsNumber.prototype); + obj.__wbg_ptr = ptr; + HoldsNumberFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + HoldsNumberFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_holdsnumber_free(ptr, 0); + } + /** + * Inner value + * @returns {number} + */ + get inner() { + const ret = wasm.holdsnumber_get_inner(this.__wbg_ptr); + return takeObject(ret); + } + /** + * Description for static_fn_with_attr + * @param {number} firstArg - some number + * @param {number} [secondArg] + * @returns {HoldsNumber} returns an instance of HoldsNumber, holding arg1 if arg2 is undefined and holding arg2 if not + */ + static static_fn_with_attr(firstArg, secondArg) { + const ret = wasm.holdsnumber_static_fn_with_attr(firstArg, addHeapObject(secondArg)); + return HoldsNumber.__wrap(ret); + } + /** + * Description for method_with_attr + * @param {number} firstArg - some number + * @param {boolean} [secondArg] + * @returns {number} returns arg1 if arg2 is true, or holding value of self if arg2 is undefined or false + */ + method_with_attr(firstArg, secondArg) { + const ret = wasm.holdsnumber_method_with_attr(this.__wbg_ptr, firstArg, addHeapObject(secondArg)); + return takeObject(ret); + } +} + +export function __wbindgen_is_falsy(arg0) { + const ret = !getObject(arg0); + return ret; +}; + +export function __wbindgen_is_undefined(arg0) { + const ret = getObject(arg0) === undefined; + return ret; +}; + +export function __wbindgen_number_new(arg0) { + const ret = arg0; + return addHeapObject(ret); +}; + +export function __wbindgen_object_clone_ref(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); +}; + +export function __wbindgen_object_drop_ref(arg0) { + takeObject(arg0); +}; + +export function __wbindgen_throw(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; + diff --git a/crates/cli/tests/reference/function-attrs.rs b/crates/cli/tests/reference/function-attrs.rs new file mode 100644 index 00000000000..05985c86297 --- /dev/null +++ b/crates/cli/tests/reference/function-attrs.rs @@ -0,0 +1,70 @@ +use wasm_bindgen::prelude::*; + +/// Description for fn_with_attr +#[wasm_bindgen( + return_type = "number", + return_description = "returns 1 if arg2 is true, or arg1 if arg2 is undefined or false" +)] +pub fn fn_with_attr( + #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, + #[wasm_bindgen(js_name = "secondArg", param_type = "boolean", optional)] arg2: JsValue, +) -> Result { + if arg2.is_undefined() { + Ok(arg1.into()) + } else if arg2.is_truthy() { + Ok(1u32.into()) + } else { + Ok(arg1.into()) + } +} + +/// Description for HoldsNumber +#[wasm_bindgen] +pub struct HoldsNumber { + inner: JsValue, +} + +#[wasm_bindgen] +impl HoldsNumber { + /// Inner value + #[wasm_bindgen(getter = "inner", return_type = "number")] + pub fn get_inner(&self) -> JsValue { + self.inner.clone() + } + + /// Description for static_fn_with_attr + #[wasm_bindgen( + return_description = "returns an instance of HoldsNumber, holding arg1 if arg2 is undefined and holding arg2 if not" + )] + pub fn static_fn_with_attr( + #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, + #[wasm_bindgen(js_name = "secondArg", param_type = "number")] + #[wasm_bindgen(optional)] + arg2: JsValue, + ) -> HoldsNumber { + if arg2.is_undefined() { + HoldsNumber { inner: arg1.into() } + } else { + HoldsNumber { inner: arg2 } + } + } + + /// Description for method_with_attr + #[wasm_bindgen( + return_type = "number", + return_description = "returns arg1 if arg2 is true, or holding value of self if arg2 is undefined or false" + )] + pub fn method_with_attr( + &self, + #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, + #[wasm_bindgen(js_name = "secondArg", param_type = "boolean", optional)] arg2: JsValue, + ) -> JsValue { + if arg2.is_undefined() { + self.inner.clone() + } else if arg2.is_truthy() { + arg1.into() + } else { + self.inner.clone() + } + } +} diff --git a/crates/cli/tests/reference/function-attrs.wat b/crates/cli/tests/reference/function-attrs.wat new file mode 100644 index 00000000000..533bbfc0375 --- /dev/null +++ b/crates/cli/tests/reference/function-attrs.wat @@ -0,0 +1,23 @@ +(module $reference_test.wasm + (type (;0;) (func (param i32) (result i32))) + (type (;1;) (func (param i32 i32))) + (type (;2;) (func (param i32 i32) (result i32))) + (type (;3;) (func (param i32 i32 i32))) + (type (;4;) (func (param i32 i32 i32) (result i32))) + (func $fn_with_attr (;0;) (type 3) (param i32 i32 i32)) + (func $holdsnumber_method_with_attr (;1;) (type 4) (param i32 i32 i32) (result i32)) + (func $holdsnumber_get_inner (;2;) (type 0) (param i32) (result i32)) + (func $holdsnumber_static_fn_with_attr (;3;) (type 2) (param i32 i32) (result i32)) + (func $__wbg_holdsnumber_free (;4;) (type 1) (param i32 i32)) + (func $__wbindgen_add_to_stack_pointer (;5;) (type 0) (param i32) (result i32)) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "fn_with_attr" (func $fn_with_attr)) + (export "__wbg_holdsnumber_free" (func $__wbg_holdsnumber_free)) + (export "holdsnumber_get_inner" (func $holdsnumber_get_inner)) + (export "holdsnumber_static_fn_with_attr" (func $holdsnumber_static_fn_with_attr)) + (export "holdsnumber_method_with_attr" (func $holdsnumber_method_with_attr)) + (export "__wbindgen_add_to_stack_pointer" (func $__wbindgen_add_to_stack_pointer)) + (@custom "target_features" (after code) "\02+\0fmutable-globals+\08sign-ext") +) + diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 46c76a1ef21..b22d1813eee 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use std::str::Chars; use ast::OperationKind; -use backend::ast::{self, ThreadLocal}; +use backend::ast::{self, FunctionAttributes, FunctionComponentAttributes, ThreadLocal}; use backend::util::{ident_ty, ShortHash}; use backend::Diagnostic; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; @@ -166,6 +166,8 @@ macro_rules! attrgen { (static_string, StaticString(Span)), (thread_local, ThreadLocal(Span)), (thread_local_v2, ThreadLocalV2(Span)), + (return_type, ReturnType(Span, String, Span)), + (return_description, ReturnDesc(Span, String, Span)), // For testing purposes only. (assert_no_shim, AssertNoShim(Span)), @@ -619,6 +621,7 @@ impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option for syn::ItemFn { self.attrs, self.vis, FunctionPosition::Free, + None, )?; attrs.check_used(); @@ -1027,6 +1031,7 @@ fn function_from_decl( attrs: Vec, vis: syn::Visibility, position: FunctionPosition, + fn_attrs: Option, ) -> Result<(ast::Function, Option), Diagnostic> { if sig.variadic.is_some() { bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions"); @@ -1153,11 +1158,108 @@ fn function_from_decl( generate_typescript: opts.skip_typescript().is_none(), generate_jsdoc: opts.skip_jsdoc().is_none(), variadic: opts.variadic().is_some(), + fn_attrs, }, method_self, )) } +/// Extracts function attributes +fn extract_fn_attrs( + sig: &mut syn::Signature, + attrs: &BindgenAttrs, +) -> Result { + let mut args_attrs = vec![]; + for input in sig.inputs.iter_mut() { + if let syn::FnArg::Typed(pat_type) = input { + let mut keep = vec![]; + let mut arg_attrs = FunctionComponentAttributes { + ty: None, + desc: None, + name: None, + optional: false, + }; + for attr in pat_type.attrs.iter() { + if !attr.path().is_ident("wasm_bindgen") { + keep.push(true); + continue; + } + keep.push(false); + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("js_name") { + if arg_attrs.name.is_some() { + return Err(meta.error("duplicate attribute")); + } + let value = meta.value()?.parse::()?.value(); + if is_js_keyword(&value) { + return Err(meta.error("collides with js/ts keywords")); + } + arg_attrs.name = Some(value); + return Ok(()); + } + if meta.path.is_ident("param_type") { + if arg_attrs.ty.is_some() { + return Err(meta.error("duplicate attribute")); + } + let value = meta.value()?.parse::()?.value(); + if is_js_keyword(&value) { + return Err(meta.error("collides with js/ts keywords")); + } + arg_attrs.ty = Some(value); + return Ok(()); + } + if meta.path.is_ident("param_description") { + if arg_attrs.desc.is_some() { + return Err(meta.error("duplicate attribute")); + } + arg_attrs.desc = Some(meta.value()?.parse::()?.value()); + return Ok(()); + } + if meta.path.is_ident("optional") { + if arg_attrs.optional { + return Err(meta.error("duplicate attribute")); + } + arg_attrs.optional = true; + return Ok(()); + } + + Err(meta.error("unrecognized wasm_bindgen param attribute, expected any of 'param_type', 'param_description', 'js_name' or 'optional'")) + })?; + } + + // in ts, non-optional args cannot come after an optional one + // this enforces the correct and valid use of "optional" attr + if !arg_attrs.optional + && args_attrs + .last() + .map(|v: &FunctionComponentAttributes| v.optional) + .unwrap_or(false) + { + bail_span!( + pat_type, + "non-optional arguments cannot be followed after an optional one" + ) + } + + // remove extracted attributes + let mut keep = keep.iter(); + pat_type.attrs.retain(|_| *keep.next().unwrap()); + + args_attrs.push(arg_attrs); + } + } + + Ok(FunctionAttributes { + args: args_attrs, + ret: FunctionComponentAttributes { + ty: attrs.return_type().map(|v| v.0.to_string()), + desc: attrs.return_description().map(|v| v.0.to_string()), + name: None, + optional: false, + }, + }) +} + pub(crate) trait MacroParse { /// Parse the contents of an object into our AST, with a context if necessary. /// @@ -1198,6 +1300,8 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { if let Some((i, _)) = no_mangle { f.attrs.remove(i); } + // extract fn components attributes before parsing to tokens stream + let fn_attrs = extract_fn_attrs(&mut f.sig, &opts)?; let comments = extract_doc_comments(&f.attrs); // If the function isn't used for anything other than being exported to JS, // it'll be unused when not building for the Wasm target and produce a @@ -1218,9 +1322,13 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { }); let rust_name = f.sig.ident.clone(); let start = opts.start().is_some(); + let mut function = f.convert(opts)?; + // set fn components attributes that was extracted previously + function.fn_attrs = Some(fn_attrs); + program.exports.push(ast::Export { comments, - function: f.convert(opts)?, + function, js_class: None, method_kind, method_self: None, @@ -1409,6 +1517,7 @@ impl MacroParse<&ClassMarker> for &mut syn::ImplItemFn { let opts = BindgenAttrs::find(&mut self.attrs)?; let comments = extract_doc_comments(&self.attrs); + let fn_attrs = extract_fn_attrs(&mut self.sig, &opts)?; let (function, method_self) = function_from_decl( &self.sig.ident, &opts, @@ -1416,6 +1525,7 @@ impl MacroParse<&ClassMarker> for &mut syn::ImplItemFn { self.attrs.clone(), self.vis.clone(), FunctionPosition::Impl { self_ty: class }, + Some(fn_attrs), )?; let method_kind = if opts.constructor().is_some() { ast::MethodKind::Constructor diff --git a/crates/macro/ui-tests/optional-function-arg-attr.rs b/crates/macro/ui-tests/optional-function-arg-attr.rs new file mode 100644 index 00000000000..8d979e2cd01 --- /dev/null +++ b/crates/macro/ui-tests/optional-function-arg-attr.rs @@ -0,0 +1,14 @@ +#![deny(unused_variables)] + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub async fn fn_with_attr( + a: u32, + #[wasm_bindgen(optional)] b: JsValue, + c: bool, +) -> Result<(), JsValue> { + Ok(()) +} + +fn main() {} diff --git a/crates/macro/ui-tests/optional-function-arg-attr.stderr b/crates/macro/ui-tests/optional-function-arg-attr.stderr new file mode 100644 index 00000000000..6bf7651485f --- /dev/null +++ b/crates/macro/ui-tests/optional-function-arg-attr.stderr @@ -0,0 +1,5 @@ +error: non-optional arguments cannot be followed after an optional one + --> ui-tests/optional-function-arg-attr.rs:9:5 + | +9 | c: bool, + | ^^^^^^^ diff --git a/crates/macro/ui-tests/unused-attributes.rs b/crates/macro/ui-tests/unused-attributes.rs index f517dcc4b55..715828f03b9 100644 --- a/crates/macro/ui-tests/unused-attributes.rs +++ b/crates/macro/ui-tests/unused-attributes.rs @@ -29,4 +29,24 @@ impl MyStruct { } } +#[wasm_bindgen(return_type = "something", return_description = "something")] +struct B {} + +#[wasm_bindgen(return_type = "something", return_description = "something")] +impl B { + #[wasm_bindgen] + pub fn foo() {} +} + +#[wasm_bindgen(return_type = "something", return_description = "something")] +pub enum D { + Variat +} + +#[wasm_bindgen(return_type = "something", return_description = "something")] +impl D { + #[wasm_bindgen] + pub fn foo() {} +} + fn main() {} diff --git a/crates/macro/ui-tests/unused-attributes.stderr b/crates/macro/ui-tests/unused-attributes.stderr index 1df8f26a021..2fa17e488c9 100644 --- a/crates/macro/ui-tests/unused-attributes.stderr +++ b/crates/macro/ui-tests/unused-attributes.stderr @@ -39,3 +39,51 @@ error: unused variable: `final` | 24 | #[wasm_bindgen(getter_with_clone, final)] | ^^^^^ help: if this is intentional, prefix it with an underscore: `_final` + +error: unused variable: `return_type` + --> ui-tests/unused-attributes.rs:32:16 + | +32 | #[wasm_bindgen(return_type = "something", return_description = "something")] + | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_type` + +error: unused variable: `return_description` + --> ui-tests/unused-attributes.rs:32:43 + | +32 | #[wasm_bindgen(return_type = "something", return_description = "something")] + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` + +error: unused variable: `return_type` + --> ui-tests/unused-attributes.rs:35:16 + | +35 | #[wasm_bindgen(return_type = "something", return_description = "something")] + | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_type` + +error: unused variable: `return_description` + --> ui-tests/unused-attributes.rs:35:43 + | +35 | #[wasm_bindgen(return_type = "something", return_description = "something")] + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` + +error: unused variable: `return_type` + --> ui-tests/unused-attributes.rs:41:16 + | +41 | #[wasm_bindgen(return_type = "something", return_description = "something")] + | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_type` + +error: unused variable: `return_description` + --> ui-tests/unused-attributes.rs:41:43 + | +41 | #[wasm_bindgen(return_type = "something", return_description = "something")] + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` + +error: unused variable: `return_type` + --> ui-tests/unused-attributes.rs:46:16 + | +46 | #[wasm_bindgen(return_type = "something", return_description = "something")] + | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_type` + +error: unused variable: `return_description` + --> ui-tests/unused-attributes.rs:46:43 + | +46 | #[wasm_bindgen(return_type = "something", return_description = "something")] + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index c5d46f1bad1..4d539767831 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -141,6 +141,18 @@ macro_rules! shared_api { generate_typescript: bool, generate_jsdoc: bool, variadic: bool, + fn_attrs: Option>, + } + + struct FunctionAttributes<'a> { + ret: FunctionComponentAttributes<'a>, + args: Vec>, + } + + struct FunctionComponentAttributes<'a> { + ty: Option<&'a str>, + desc: Option<&'a str>, + optional: bool, } struct Struct<'a> { diff --git a/crates/typescript-tests/src/function_attrs.rs b/crates/typescript-tests/src/function_attrs.rs new file mode 100644 index 00000000000..7de458a8a6f --- /dev/null +++ b/crates/typescript-tests/src/function_attrs.rs @@ -0,0 +1,70 @@ +use wasm_bindgen::prelude::*; + +/// Description for fn_with_attr +#[wasm_bindgen( + return_type = "number", + return_description = "returns 1 if arg2 is true, or arg1 if arg2 is undefined or false" +)] +pub async fn fn_with_attr( + #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, + #[wasm_bindgen(js_name = "secondArg", param_type = "boolean", optional)] arg2: JsValue, +) -> Result { + if arg2.is_undefined() { + Ok(arg1.into()) + } else if arg2.is_truthy() { + Ok(1u32.into()) + } else { + Ok(arg1.into()) + } +} + +/// Description for HoldsNumber +#[wasm_bindgen] +pub struct HoldsNumber { + inner: JsValue, +} + +#[wasm_bindgen] +impl HoldsNumber { + /// Inner value + #[wasm_bindgen(getter = "inner", return_type = "number")] + pub fn get_inner(&self) -> JsValue { + self.inner.clone() + } + + /// Description for static_fn_with_attr + #[wasm_bindgen( + return_description = "returns an instance of HoldsNumber, holding arg1 if arg2 is undefined and holding arg2 if not" + )] + pub fn static_fn_with_attr( + #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, + #[wasm_bindgen(js_name = "secondArg", param_type = "number")] + #[wasm_bindgen(optional)] + arg2: JsValue, + ) -> HoldsNumber { + if arg2.is_undefined() { + HoldsNumber { inner: arg1.into() } + } else { + HoldsNumber { inner: arg2 } + } + } + + /// Description for method_with_attr + #[wasm_bindgen( + return_type = "number", + return_description = "returns arg1 if arg2 is true, or holding value of self if arg2 is undefined or false" + )] + pub fn method_with_attr( + &self, + #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, + #[wasm_bindgen(js_name = "secondArg", param_type = "boolean", optional)] arg2: JsValue, + ) -> JsValue { + if arg2.is_undefined() { + self.inner.clone() + } else if arg2.is_truthy() { + arg1.into() + } else { + self.inner.clone() + } + } +} diff --git a/crates/typescript-tests/src/function_attrs.ts b/crates/typescript-tests/src/function_attrs.ts new file mode 100644 index 00000000000..c85e18bf98a --- /dev/null +++ b/crates/typescript-tests/src/function_attrs.ts @@ -0,0 +1,37 @@ +import * as wbg from '../pkg/typescript_tests'; +import * as wasm from '../pkg/typescript_tests_bg.wasm'; +import { expect, test } from "@jest/globals"; + +const wasm_fn_with_attr: (a: number, b?: number) => number = wasm.fn_with_attr; +const wbg_fn_with_attr: (a: number, b?: boolean) => Promise = wbg.fn_with_attr; +const wasm_holdsnumber_static_fn_with_attr: (a: number, b?: number) => number = wasm.holdsnumber_static_fn_with_attr; +const wbg_holdsnumber_static_fn_with_attr: (a1: number, b?: number) => wbg.HoldsNumber = wbg.HoldsNumber.static_fn_with_attr; +const wasm_holdsnumber_method_with_attr: (a: number, b: number, c: number) => number = wasm.holdsnumber_method_with_attr; +const wbg_holdsnumber_method_with_attr: (a: number, b?: boolean) => number = wbg.HoldsNumber.static_fn_with_attr(1).method_with_attr; + +test("async function fn_with_attr", async () => { + let result = await wbg.fn_with_attr(4); + expect(result).toEqual(4); + + result = await wbg.fn_with_attr(5, false); + expect(result).toEqual(5); + + result = await wbg.fn_with_attr(6, true); + expect(result).toEqual(1); +}); + +test("HoldsNumber methods", () => { + const num1 = wbg.HoldsNumber.static_fn_with_attr(4).inner; + const num2 = wbg.HoldsNumber.static_fn_with_attr(3, 4).inner; + expect(num1).toEqual(num2); + + const holdsNumber = wbg.HoldsNumber.static_fn_with_attr(8); + let result = holdsNumber.method_with_attr(4); + expect(result).toEqual(8); + + result = holdsNumber.method_with_attr(5, false); + expect(result).toEqual(8); + + result = holdsNumber.method_with_attr(6, true); + expect(result).toEqual(6); +}); \ No newline at end of file diff --git a/crates/typescript-tests/src/lib.rs b/crates/typescript-tests/src/lib.rs index 96fc3e2107c..b4edbadb4aa 100644 --- a/crates/typescript-tests/src/lib.rs +++ b/crates/typescript-tests/src/lib.rs @@ -3,6 +3,7 @@ pub mod custom_section; pub mod enums; +pub mod function_attrs; pub mod getters_setters; pub mod inspectable; pub mod omit_definition; diff --git a/guide/src/reference/attributes/on-rust-exports/function-attributes.md b/guide/src/reference/attributes/on-rust-exports/function-attributes.md new file mode 100644 index 00000000000..2f48d0150e1 --- /dev/null +++ b/guide/src/reference/attributes/on-rust-exports/function-attributes.md @@ -0,0 +1,139 @@ +# `function-attributes` + +By default, exported Rust functions and methods generate function signature from equivalent rust types identifiers without any arguments and return var documentations unless completely handcoded using `skip-jsdoc` and `typescript-custom-section` with `skip_typescript` which does not provide the best solution, specially when one needs to do a lot of those handcodings. +It's fair to say that being able to write specific documentation for each argument and return variable of the function, or override an argument or return variable type to a custom one on generated js/ts bindings and many more examples like this is essential for creating a well defined, structured and typed bindings. + +Function attributes addresses these issues and limitations by providing attributes to override a function's return variable and arguments names and types on generated bindings as well as ability to write specific documentation for each of them individually as desired: +- `#[wasm_bindgen(return_type)]` and `#[wasm_bindgen(return_description)]` used to override function's return type and to specify description on generated js/ts bindings. +- `#[wasm_bindgen(js_name)]`, `#[wasm_bindgen(param_type)]` and `#[wasm_bindgen(param_description)]` applied to a rust function argument to override that argument's name and type and to specify description on generated js/ts bindings. +- `#[wasm_bindgen(optional)]` used to tag a function's argument as optional (typescript `?` operator) on function's generated typescript signature. + +For example a rust function can return `JsValue` by serializing a rust type using serde, yet on generated ts bindings instead of `any` as the return type, it can be overriden to the ts interface of the serialized rust type equivalent defined using `typescript-custom-section` (or using [Tsify Crate](https://crates.io/crates/tsify)): +```rust +// we wont use "#[wasm_bindgen]" for this struct +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Person { + pub name: String; +} + +// define a ts interface equivalent of Person struct +// alternatively Tsify crate can be used to generate ts interface from rust types +#[wasm_bindgen(typescript_custom_section)] +const TS_INTERFACE_EXPORT: &'static str = r" + export interface Person { name: string; } +"; + +#[wasm_bindgen(return_type = "Person", return_description = "a Person object")] +pub async fn build_person( + #[wasm_bindgen(js_name = "personName", param_description = "Specifies the person's name")] + person_name: String, +) -> Result { + // + // some async operations + // + Ok(serde_wasm_bindgen::to_value(&Person { name: person_name })?) +} +``` +this will generate the following ts bindings: +```ts +export interface Person { name: string; } + +/** +* @param personName - Specifies the person's name +* @returns a Person object +*/ +export function build_person(personName: string): Promise; +``` +As you can see, using function attributes, we can now return a js/ts object without ever using `wasm_bindgen` macro on `Person` struct that would have generated a js/ts class object in the bindings which might not be the desired outcome in every case, also you can see, instead of handcoding the full documentation for `build_person()` with all js_doc/ts_doc tags and syntax, we can just write specific docs for each individual component of the function. + +Let's look at some more exmaples in details: +```rust +/// Description for foo +#[wasm_bindgen(return_type = "Foo", return_description = "some description for return type")] +pub async fn foo( + #[wasm_bindgen(js_name = "firstArg", param_description = "some description for firstArg")] + arg1: String, + #[wasm_bindgen(js_name = "secondArg", param_type = "Bar", optional)] + arg2: JsValue, +) -> Result { + // function body +} +``` +This will generate the following js bindings: +```js +/** +* Description for foo +* @param {string} firstArg - some description for arg1 +* @param {Bar} [secondArg] +* @returns {Promise} some description for return type +*/ +export function foo(firstArg, secondArg) {}; +``` +And will generate the following ts bindings: +```ts +/** +* Description for foo +* @param firstArg - some description for arg1 +* @param secondArg +* @returns some description for return type +*/ +export function foo(firstArg: string, secondArg?: Bar): Promise; +``` + +Same thing applies to rust struct's (and enums) impl methods and their equivalent js/ts class methods: +```rust +/// Description for Foo +#[wasm_bindgen] +pub struct Foo { + pub Foo: String, +} + +#[wasm_bindgen] +impl Foo { + /// Description for foo + #[wasm_bindgen(return_type = "Baz", return_description = "some description for return type")] + pub fn foo( + &self, + #[wasm_bindgen(js_name = "firstArg", param_description = "some description for firstArg")] + arg1: String, + #[wasm_bindgen(js_name = "secondArg", param_type = "Bar", optional)] + arg2: JsValue, + ) -> JsValue { + // function body + } +} +``` + +This will generate the following js bindings: +```js +/** +* Description for Foo +*/ +export class Foo { + /** + * Description for foo + * @param {string} firstArg - some description for arg1 + * @param {Bar} [secondArg] + * @returns {Baz} some description for return type + */ + foo(firstArg, secondArg) {}; +} +``` + +And will generate the following ts bindings: +```ts +/** +* Description for Foo +*/ +export class Foo { + /** + * Description for foo + * @param firstArg - some description for arg1 + * @param secondArg + * @returns some description for return type + */ + foo(firstArg: string, secondArg?: Bar): Baz; +} +``` + +As shown in exmaples, these attributes allows for great level of control and customization over generated bindings but note that they can only be used on functions and methods that are being exported to js/ts and cannot be used on `self` argument of rust structs/enums methods. \ No newline at end of file From fac43b5440fb30702be8e45fc935dc11269f1a07 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 6 Jan 2025 03:24:10 +0000 Subject: [PATCH 03/50] rm --- .../tests/reference/function-attrs-async.d.ts | 34 --------- .../tests/reference/function-attrs-async.rs | 70 ------------------- 2 files changed, 104 deletions(-) delete mode 100644 crates/cli/tests/reference/function-attrs-async.d.ts delete mode 100644 crates/cli/tests/reference/function-attrs-async.rs diff --git a/crates/cli/tests/reference/function-attrs-async.d.ts b/crates/cli/tests/reference/function-attrs-async.d.ts deleted file mode 100644 index ca79c04a4e5..00000000000 --- a/crates/cli/tests/reference/function-attrs-async.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * Description for fn_with_attr - * @param firstArg - some number - * @param secondArg - * @returns returns 1 if arg2 is true, or arg1 if arg2 is undefined or false - */ -export function fn_with_attr(firstArg: number, secondArg?: boolean): Promise; -/** - * Description for HoldsNumber - */ -export class HoldsNumber { - private constructor(); - free(): void; - /** - * Description for static_fn_with_attr - * @param firstArg - some number - * @param secondArg - * @returns returns an instance of HoldsNumber, holding arg1 if arg2 is undefined and holding arg2 if not - */ - static static_fn_with_attr(firstArg: number, secondArg?: number): Promise; - /** - * Description for method_with_attr - * @param firstArg - some number - * @param secondArg - * @returns returns arg1 if arg2 is true, or holding value of self if arg2 is undefined or false - */ - method_with_attr(firstArg: number, secondArg?: boolean): Promise; - /** - * Inner value - */ - readonly inner: number; -} diff --git a/crates/cli/tests/reference/function-attrs-async.rs b/crates/cli/tests/reference/function-attrs-async.rs deleted file mode 100644 index 66b82164234..00000000000 --- a/crates/cli/tests/reference/function-attrs-async.rs +++ /dev/null @@ -1,70 +0,0 @@ -use wasm_bindgen::prelude::*; - -/// Description for fn_with_attr -#[wasm_bindgen( - return_type = "number", - return_description = "returns 1 if arg2 is true, or arg1 if arg2 is undefined or false" -)] -pub async fn fn_with_attr( - #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "boolean", optional)] arg2: JsValue, -) -> Result { - if arg2.is_undefined() { - Ok(arg1.into()) - } else if arg2.is_truthy() { - Ok(1u32.into()) - } else { - Ok(arg1.into()) - } -} - -/// Description for HoldsNumber -#[wasm_bindgen] -pub struct HoldsNumber { - inner: JsValue, -} - -#[wasm_bindgen] -impl HoldsNumber { - /// Inner value - #[wasm_bindgen(getter = "inner", return_type = "number")] - pub fn get_inner(&self) -> JsValue { - self.inner.clone() - } - - /// Description for static_fn_with_attr - #[wasm_bindgen( - return_description = "returns an instance of HoldsNumber, holding arg1 if arg2 is undefined and holding arg2 if not" - )] - pub async fn static_fn_with_attr( - #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "number")] - #[wasm_bindgen(optional)] - arg2: JsValue, - ) -> Result { - if arg2.is_undefined() { - Ok(HoldsNumber { inner: arg1.into() }) - } else { - Ok(HoldsNumber { inner: arg2 }) - } - } - - /// Description for method_with_attr - #[wasm_bindgen( - return_type = "number", - return_description = "returns arg1 if arg2 is true, or holding value of self if arg2 is undefined or false" - )] - pub async fn method_with_attr( - &self, - #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "boolean", optional)] arg2: JsValue, - ) -> Result { - if arg2.is_undefined() { - Ok(self.inner.clone()) - } else if arg2.is_truthy() { - Ok(arg1.into()) - } else { - Ok(self.inner.clone()) - } - } -} From de4e4498e3b318425a883d2459809c0f44c63e90 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 6 Jan 2025 03:45:57 +0000 Subject: [PATCH 04/50] fix test --- .../cli/tests/reference/function-attrs.d.ts | 2 +- crates/cli/tests/reference/function-attrs.js | 170 ------------------ crates/cli/tests/reference/function-attrs.rs | 2 +- crates/cli/tests/reference/function-attrs.wat | 23 --- 4 files changed, 2 insertions(+), 195 deletions(-) delete mode 100644 crates/cli/tests/reference/function-attrs.js delete mode 100644 crates/cli/tests/reference/function-attrs.wat diff --git a/crates/cli/tests/reference/function-attrs.d.ts b/crates/cli/tests/reference/function-attrs.d.ts index 13ae4389479..f33b6414fac 100644 --- a/crates/cli/tests/reference/function-attrs.d.ts +++ b/crates/cli/tests/reference/function-attrs.d.ts @@ -6,7 +6,7 @@ * @param secondArg * @returns returns 1 if arg2 is true, or arg1 if arg2 is undefined or false */ -export function fn_with_attr(firstArg: number, secondArg?: boolean): number; +export function fn_with_attr(firstArg: number, secondArg?: boolean): Promise; /** * Description for HoldsNumber */ diff --git a/crates/cli/tests/reference/function-attrs.js b/crates/cli/tests/reference/function-attrs.js deleted file mode 100644 index e90973bf2a4..00000000000 --- a/crates/cli/tests/reference/function-attrs.js +++ /dev/null @@ -1,170 +0,0 @@ -let wasm; -export function __wbg_set_wasm(val) { - wasm = val; -} - - -const heap = new Array(128).fill(undefined); - -heap.push(undefined, null, true, false); - -function getObject(idx) { return heap[idx]; } - -let heap_next = heap.length; - -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1); - const idx = heap_next; - heap_next = heap[idx]; - - heap[idx] = obj; - return idx; -} - -function dropObject(idx) { - if (idx < 132) return; - heap[idx] = heap_next; - heap_next = idx; -} - -function takeObject(idx) { - const ret = getObject(idx); - dropObject(idx); - return ret; -} - -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)); -} - -let cachedDataViewMemory0 = null; - -function getDataViewMemory0() { - if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { - cachedDataViewMemory0 = new DataView(wasm.memory.buffer); - } - return cachedDataViewMemory0; -} -/** - * Description for fn_with_attr - * @param {number} firstArg - some number - * @param {boolean} [secondArg] - * @returns {number} returns 1 if arg2 is true, or arg1 if arg2 is undefined or false - */ -export function fn_with_attr(firstArg, secondArg) { - try { - const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); - wasm.fn_with_attr(retptr, firstArg, addHeapObject(secondArg)); - var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); - var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); - var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); - if (r2) { - throw takeObject(r1); - } - return takeObject(r0); - } finally { - wasm.__wbindgen_add_to_stack_pointer(16); - } -} - -const HoldsNumberFinalization = (typeof FinalizationRegistry === 'undefined') - ? { register: () => {}, unregister: () => {} } - : new FinalizationRegistry(ptr => wasm.__wbg_holdsnumber_free(ptr >>> 0, 1)); -/** - * Description for HoldsNumber - */ -export class HoldsNumber { - - static __wrap(ptr) { - ptr = ptr >>> 0; - const obj = Object.create(HoldsNumber.prototype); - obj.__wbg_ptr = ptr; - HoldsNumberFinalization.register(obj, obj.__wbg_ptr, obj); - return obj; - } - - __destroy_into_raw() { - const ptr = this.__wbg_ptr; - this.__wbg_ptr = 0; - HoldsNumberFinalization.unregister(this); - return ptr; - } - - free() { - const ptr = this.__destroy_into_raw(); - wasm.__wbg_holdsnumber_free(ptr, 0); - } - /** - * Inner value - * @returns {number} - */ - get inner() { - const ret = wasm.holdsnumber_get_inner(this.__wbg_ptr); - return takeObject(ret); - } - /** - * Description for static_fn_with_attr - * @param {number} firstArg - some number - * @param {number} [secondArg] - * @returns {HoldsNumber} returns an instance of HoldsNumber, holding arg1 if arg2 is undefined and holding arg2 if not - */ - static static_fn_with_attr(firstArg, secondArg) { - const ret = wasm.holdsnumber_static_fn_with_attr(firstArg, addHeapObject(secondArg)); - return HoldsNumber.__wrap(ret); - } - /** - * Description for method_with_attr - * @param {number} firstArg - some number - * @param {boolean} [secondArg] - * @returns {number} returns arg1 if arg2 is true, or holding value of self if arg2 is undefined or false - */ - method_with_attr(firstArg, secondArg) { - const ret = wasm.holdsnumber_method_with_attr(this.__wbg_ptr, firstArg, addHeapObject(secondArg)); - return takeObject(ret); - } -} - -export function __wbindgen_is_falsy(arg0) { - const ret = !getObject(arg0); - return ret; -}; - -export function __wbindgen_is_undefined(arg0) { - const ret = getObject(arg0) === undefined; - return ret; -}; - -export function __wbindgen_number_new(arg0) { - const ret = arg0; - return addHeapObject(ret); -}; - -export function __wbindgen_object_clone_ref(arg0) { - const ret = getObject(arg0); - return addHeapObject(ret); -}; - -export function __wbindgen_object_drop_ref(arg0) { - takeObject(arg0); -}; - -export function __wbindgen_throw(arg0, arg1) { - throw new Error(getStringFromWasm0(arg0, arg1)); -}; - diff --git a/crates/cli/tests/reference/function-attrs.rs b/crates/cli/tests/reference/function-attrs.rs index 05985c86297..7de458a8a6f 100644 --- a/crates/cli/tests/reference/function-attrs.rs +++ b/crates/cli/tests/reference/function-attrs.rs @@ -5,7 +5,7 @@ use wasm_bindgen::prelude::*; return_type = "number", return_description = "returns 1 if arg2 is true, or arg1 if arg2 is undefined or false" )] -pub fn fn_with_attr( +pub async fn fn_with_attr( #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, #[wasm_bindgen(js_name = "secondArg", param_type = "boolean", optional)] arg2: JsValue, ) -> Result { diff --git a/crates/cli/tests/reference/function-attrs.wat b/crates/cli/tests/reference/function-attrs.wat deleted file mode 100644 index 533bbfc0375..00000000000 --- a/crates/cli/tests/reference/function-attrs.wat +++ /dev/null @@ -1,23 +0,0 @@ -(module $reference_test.wasm - (type (;0;) (func (param i32) (result i32))) - (type (;1;) (func (param i32 i32))) - (type (;2;) (func (param i32 i32) (result i32))) - (type (;3;) (func (param i32 i32 i32))) - (type (;4;) (func (param i32 i32 i32) (result i32))) - (func $fn_with_attr (;0;) (type 3) (param i32 i32 i32)) - (func $holdsnumber_method_with_attr (;1;) (type 4) (param i32 i32 i32) (result i32)) - (func $holdsnumber_get_inner (;2;) (type 0) (param i32) (result i32)) - (func $holdsnumber_static_fn_with_attr (;3;) (type 2) (param i32 i32) (result i32)) - (func $__wbg_holdsnumber_free (;4;) (type 1) (param i32 i32)) - (func $__wbindgen_add_to_stack_pointer (;5;) (type 0) (param i32) (result i32)) - (memory (;0;) 17) - (export "memory" (memory 0)) - (export "fn_with_attr" (func $fn_with_attr)) - (export "__wbg_holdsnumber_free" (func $__wbg_holdsnumber_free)) - (export "holdsnumber_get_inner" (func $holdsnumber_get_inner)) - (export "holdsnumber_static_fn_with_attr" (func $holdsnumber_static_fn_with_attr)) - (export "holdsnumber_method_with_attr" (func $holdsnumber_method_with_attr)) - (export "__wbindgen_add_to_stack_pointer" (func $__wbindgen_add_to_stack_pointer)) - (@custom "target_features" (after code) "\02+\0fmutable-globals+\08sign-ext") -) - From 11c257156f4e88e62da49adbdde00855074592d4 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 6 Jan 2025 04:01:13 +0000 Subject: [PATCH 05/50] fix schema hash --- crates/shared/src/schema_hash_approval.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/shared/src/schema_hash_approval.rs b/crates/shared/src/schema_hash_approval.rs index be50d273a1f..96bd4a940b5 100644 --- a/crates/shared/src/schema_hash_approval.rs +++ b/crates/shared/src/schema_hash_approval.rs @@ -8,7 +8,7 @@ // If the schema in this library has changed then: // 1. Bump the version in `crates/shared/Cargo.toml` // 2. Change the `SCHEMA_VERSION` in this library to this new Cargo.toml version -const APPROVED_SCHEMA_FILE_HASH: &str = "12659088193118507901"; +const APPROVED_SCHEMA_FILE_HASH: &str = "9634893679140845804"; #[test] fn schema_version() { From c43561e6c4c8f6757f0b0ee69abe0cb8dbd44550 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 6 Jan 2025 04:09:32 +0000 Subject: [PATCH 06/50] update shared schema version --- crates/shared/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 4d539767831..4b8d3aeccac 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -6,7 +6,7 @@ mod schema_hash_approval; // This gets changed whenever our schema changes. // At this time versions of wasm-bindgen and wasm-bindgen-cli are required to have the exact same // SCHEMA_VERSION in order to work together. -pub const SCHEMA_VERSION: &str = "0.2.98"; +pub const SCHEMA_VERSION: &str = "0.2.99"; #[macro_export] macro_rules! shared_api { From 003312f394e90d0f5e66d7ae1fb73552595cf499 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 6 Jan 2025 04:16:22 +0000 Subject: [PATCH 07/50] Update schema_hash_approval.rs --- crates/shared/src/schema_hash_approval.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/shared/src/schema_hash_approval.rs b/crates/shared/src/schema_hash_approval.rs index 96bd4a940b5..d4de289bef4 100644 --- a/crates/shared/src/schema_hash_approval.rs +++ b/crates/shared/src/schema_hash_approval.rs @@ -8,7 +8,7 @@ // If the schema in this library has changed then: // 1. Bump the version in `crates/shared/Cargo.toml` // 2. Change the `SCHEMA_VERSION` in this library to this new Cargo.toml version -const APPROVED_SCHEMA_FILE_HASH: &str = "9634893679140845804"; +const APPROVED_SCHEMA_FILE_HASH: &str = "2677741315808308343"; #[test] fn schema_version() { From e1115512fe58586239ce323e5f292ec0ec12d7a7 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 6 Jan 2025 05:04:25 +0000 Subject: [PATCH 08/50] update --- crates/backend/src/encode.rs | 8 ++++---- crates/cli-support/src/decode.rs | 16 +++++++++++++++- crates/cli-support/src/js/binding.rs | 9 +++++---- crates/cli-support/src/wit/mod.rs | 17 +---------------- crates/cli-support/src/wit/nonstandard.rs | 22 +++------------------- crates/shared/src/lib.rs | 14 +++++++------- crates/shared/src/schema_hash_approval.rs | 2 +- 7 files changed, 36 insertions(+), 52 deletions(-) diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index a443a72cc29..590bdb1c04b 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -236,16 +236,16 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi .collect::>(); let fn_attrs = func.fn_attrs.as_ref().map(|attrs| FunctionAttributes { ret: FunctionComponentAttributes { - ty: attrs.ret.ty.as_deref(), - desc: attrs.ret.desc.as_deref(), + ty: attrs.ret.ty.clone(), + desc: attrs.ret.desc.clone(), optional: false, }, args: attrs .args .iter() .map(|arg_attr| FunctionComponentAttributes { - ty: arg_attr.ty.as_deref(), - desc: arg_attr.desc.as_deref(), + ty: arg_attr.ty.clone(), + desc: arg_attr.desc.clone(), optional: arg_attr.optional, }) .collect::>(), diff --git a/crates/cli-support/src/decode.rs b/crates/cli-support/src/decode.rs index f527acea2d2..a3135cbed90 100644 --- a/crates/cli-support/src/decode.rs +++ b/crates/cli-support/src/decode.rs @@ -1,4 +1,4 @@ -use std::{ops::Deref, str}; +use std::{fmt::Debug, ops::Deref, str}; pub trait Decode<'src>: Sized { fn decode(data: &mut &'src [u8]) -> Self; @@ -94,6 +94,20 @@ impl<'src, T: Decode<'src>> Decode<'src> for Option { } } +impl Debug for FunctionAttributes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("ret {:?}, args {:?}", self.ret, self.args)) + } +} +impl Debug for FunctionComponentAttributes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!( + "ty {:?}, desc {:?}, optional {}", + self.ty, self.desc, self.optional + )) + } +} + macro_rules! decode_struct { ($name:ident ($($lt:tt)*) $($field:ident: $ty:ty,)*) => { pub struct $name <$($lt)*> { diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index ed52eef5bcf..1082ac817d8 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -4,9 +4,10 @@ //! exported functions, table elements, imports, etc. All function shims //! generated by `wasm-bindgen` run through this type. +use crate::decode::FunctionAttributes; use crate::js::Context; +use crate::wit::InstructionData; use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction}; -use crate::wit::{AuxExportFnAttrs, InstructionData}; use anyhow::{anyhow, bail, Error}; use std::collections::HashSet; use std::fmt::Write; @@ -126,7 +127,7 @@ impl<'a, 'b> Builder<'a, 'b> { variadic: bool, generate_jsdoc: bool, debug_name: &str, - export_fn_attrs: &Option, + export_fn_attrs: &Option, ) -> Result { if self .cx @@ -331,7 +332,7 @@ impl<'a, 'b> Builder<'a, 'b> { might_be_optional_field: &mut bool, asyncness: bool, variadic: bool, - export_fn_attrs: &Option, + export_fn_attrs: &Option, ) -> (String, Vec, Option, HashSet) { // flatten args types overrides let args_overrides = export_fn_attrs @@ -453,7 +454,7 @@ impl<'a, 'b> Builder<'a, 'b> { arg_tys: &[&AdapterType], ts_ret: &Option, variadic: bool, - export_fn_attrs: &Option, + export_fn_attrs: &Option, is_ts: bool, ) -> String { // flatten args attributes overrides diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 0ec18cd6e5c..2890d10e66e 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -533,22 +533,7 @@ impl<'a> Context<'a> { generate_typescript: export.function.generate_typescript, generate_jsdoc: export.function.generate_jsdoc, variadic: export.function.variadic, - export_fn_attrs: export.function.fn_attrs.map(|attrs| AuxExportFnAttrs { - ret: AuxExportFnCompAttrs { - ty: attrs.ret.ty.map(String::from), - desc: attrs.ret.desc.map(String::from), - optional: false, - }, - args: attrs - .args - .iter() - .map(|arg_attr| AuxExportFnCompAttrs { - ty: arg_attr.ty.map(String::from), - desc: arg_attr.desc.map(String::from), - optional: arg_attr.optional, - }) - .collect::>(), - }), + export_fn_attrs: export.function.fn_attrs, }, ); Ok(()) diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index 1076538283a..dac7400d922 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -1,7 +1,9 @@ +use crate::decode::FunctionAttributes; use crate::intrinsic::Intrinsic; use crate::wit::AdapterId; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; +use std::fmt::Debug; use std::path::PathBuf; use walrus::TypedCustomSectionId; @@ -87,25 +89,7 @@ pub struct AuxExport { /// Whether typescript bindings should be generated for this export. pub variadic: bool, /// Extra info about an exporting function components attributes - pub export_fn_attrs: Option, -} - -#[derive(Clone, Debug)] -pub struct AuxExportFnAttrs { - /// Exporting function's return attributes - pub ret: AuxExportFnCompAttrs, - /// Exporting function's arguments attributes - pub args: Vec, -} - -#[derive(Clone, Debug)] -pub struct AuxExportFnCompAttrs { - /// Specifies the type for a function component - pub ty: Option, - /// Description of the function component - pub desc: Option, - /// Specifies if the component is optional (used for function arguments) - pub optional: bool, + pub export_fn_attrs: Option, } /// All possible kinds of exports from a Wasm module. diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 4b8d3aeccac..d30b031504f 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -141,17 +141,17 @@ macro_rules! shared_api { generate_typescript: bool, generate_jsdoc: bool, variadic: bool, - fn_attrs: Option>, + fn_attrs: Option, } - struct FunctionAttributes<'a> { - ret: FunctionComponentAttributes<'a>, - args: Vec>, + struct FunctionAttributes { + ret: FunctionComponentAttributes, + args: Vec, } - struct FunctionComponentAttributes<'a> { - ty: Option<&'a str>, - desc: Option<&'a str>, + struct FunctionComponentAttributes { + ty: Option, + desc: Option, optional: bool, } diff --git a/crates/shared/src/schema_hash_approval.rs b/crates/shared/src/schema_hash_approval.rs index d4de289bef4..770b91bcd85 100644 --- a/crates/shared/src/schema_hash_approval.rs +++ b/crates/shared/src/schema_hash_approval.rs @@ -8,7 +8,7 @@ // If the schema in this library has changed then: // 1. Bump the version in `crates/shared/Cargo.toml` // 2. Change the `SCHEMA_VERSION` in this library to this new Cargo.toml version -const APPROVED_SCHEMA_FILE_HASH: &str = "2677741315808308343"; +const APPROVED_SCHEMA_FILE_HASH: &str = "11919406220845350687"; #[test] fn schema_version() { From 59faf699f1ab6b860cba4303aa3259de9df1e9a4 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 6 Jan 2025 05:26:13 +0000 Subject: [PATCH 09/50] docs --- .../attributes/on-rust-exports/function-attributes.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/guide/src/reference/attributes/on-rust-exports/function-attributes.md b/guide/src/reference/attributes/on-rust-exports/function-attributes.md index 2f48d0150e1..07fd2e4d49f 100644 --- a/guide/src/reference/attributes/on-rust-exports/function-attributes.md +++ b/guide/src/reference/attributes/on-rust-exports/function-attributes.md @@ -1,12 +1,12 @@ # `function-attributes` By default, exported Rust functions and methods generate function signature from equivalent rust types identifiers without any arguments and return var documentations unless completely handcoded using `skip-jsdoc` and `typescript-custom-section` with `skip_typescript` which does not provide the best solution, specially when one needs to do a lot of those handcodings. -It's fair to say that being able to write specific documentation for each argument and return variable of the function, or override an argument or return variable type to a custom one on generated js/ts bindings and many more examples like this is essential for creating a well defined, structured and typed bindings. +It's fair to say that being able to write specific documentation for each argument and return variable of the function, or override an argument or return variable type to a custom one for generated js/ts bindings and many more use cases are essential for creating a well defined, structured and typed bindings. -Function attributes addresses these issues and limitations by providing attributes to override a function's return variable and arguments names and types on generated bindings as well as ability to write specific documentation for each of them individually as desired: -- `#[wasm_bindgen(return_type)]` and `#[wasm_bindgen(return_description)]` used to override function's return type and to specify description on generated js/ts bindings. -- `#[wasm_bindgen(js_name)]`, `#[wasm_bindgen(param_type)]` and `#[wasm_bindgen(param_description)]` applied to a rust function argument to override that argument's name and type and to specify description on generated js/ts bindings. -- `#[wasm_bindgen(optional)]` used to tag a function's argument as optional (typescript `?` operator) on function's generated typescript signature. +Function attributes addresses these issues and limitations by providing an ability to override a function's return type and arguments names and types for generated bindings as well as ability to write specific documentation for each of them individually as desired: +- `#[wasm_bindgen(return_type)]` and `#[wasm_bindgen(return_description)]` used to override function's return type and to specify description for generated js/ts bindings. +- `#[wasm_bindgen(js_name)]`, `#[wasm_bindgen(param_type)]` and `#[wasm_bindgen(param_description)]` applied to a rust function argument to override that argument's name and type and to specify description for generated js/ts bindings. +- `#[wasm_bindgen(optional)]` used to tag a function's argument as optional (typescript `?` operator) for function's generated typescript signature. For example a rust function can return `JsValue` by serializing a rust type using serde, yet on generated ts bindings instead of `any` as the return type, it can be overriden to the ts interface of the serialized rust type equivalent defined using `typescript-custom-section` (or using [Tsify Crate](https://crates.io/crates/tsify)): ```rust From c1152221d6a2ba414fecbd029be95a3c44fc1d3e Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 6 Jan 2025 05:29:10 +0000 Subject: [PATCH 10/50] docs --- .../reference/attributes/on-rust-exports/function-attributes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/reference/attributes/on-rust-exports/function-attributes.md b/guide/src/reference/attributes/on-rust-exports/function-attributes.md index 07fd2e4d49f..c2b060a5772 100644 --- a/guide/src/reference/attributes/on-rust-exports/function-attributes.md +++ b/guide/src/reference/attributes/on-rust-exports/function-attributes.md @@ -44,7 +44,7 @@ export interface Person { name: string; } */ export function build_person(personName: string): Promise; ``` -As you can see, using function attributes, we can now return a js/ts object without ever using `wasm_bindgen` macro on `Person` struct that would have generated a js/ts class object in the bindings which might not be the desired outcome in every case, also you can see, instead of handcoding the full documentation for `build_person()` with all js_doc/ts_doc tags and syntax, we can just write specific docs for each individual component of the function. +As you can see, using function attributes, we can now return a js/ts object without ever using `wasm_bindgen` macro on `Person` struct that would have generated a js/ts class object in the bindings which might not be the desired outcome for every case, you can see as well that instead of handcoding the full documentation for `build_person()` with all js_doc/ts_doc tags and syntax, we can just write specific docs for each individual component of the function and as a result end up with a fully typed and documented function in the bindings. Let's look at some more exmaples in details: ```rust From 13acbe4048e58b1de5809a195b8d0f92a202e83b Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 6 Jan 2025 05:30:27 +0000 Subject: [PATCH 11/50] docs --- .../attributes/on-rust-exports/function-attributes.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/guide/src/reference/attributes/on-rust-exports/function-attributes.md b/guide/src/reference/attributes/on-rust-exports/function-attributes.md index c2b060a5772..d5038aa8ca1 100644 --- a/guide/src/reference/attributes/on-rust-exports/function-attributes.md +++ b/guide/src/reference/attributes/on-rust-exports/function-attributes.md @@ -63,7 +63,7 @@ This will generate the following js bindings: ```js /** * Description for foo -* @param {string} firstArg - some description for arg1 +* @param {string} firstArg - some description for firstArg * @param {Bar} [secondArg] * @returns {Promise} some description for return type */ @@ -73,7 +73,7 @@ And will generate the following ts bindings: ```ts /** * Description for foo -* @param firstArg - some description for arg1 +* @param firstArg - some description for firstArg * @param secondArg * @returns some description for return type */ @@ -112,7 +112,7 @@ This will generate the following js bindings: export class Foo { /** * Description for foo - * @param {string} firstArg - some description for arg1 + * @param {string} firstArg - some description for firstArg * @param {Bar} [secondArg] * @returns {Baz} some description for return type */ @@ -128,7 +128,7 @@ And will generate the following ts bindings: export class Foo { /** * Description for foo - * @param firstArg - some description for arg1 + * @param firstArg - some description for firstArg * @param secondArg * @returns some description for return type */ From 73d39b638ee0685ed6b40047e7600dea633c795b Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 6 Jan 2025 15:58:59 +0000 Subject: [PATCH 12/50] fix debug impl --- crates/cli-support/src/decode.rs | 8 ++++++-- crates/typescript-tests/run.sh | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/cli-support/src/decode.rs b/crates/cli-support/src/decode.rs index a3135cbed90..da84b162d86 100644 --- a/crates/cli-support/src/decode.rs +++ b/crates/cli-support/src/decode.rs @@ -96,13 +96,17 @@ impl<'src, T: Decode<'src>> Decode<'src> for Option { impl Debug for FunctionAttributes { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("ret {:?}, args {:?}", self.ret, self.args)) + f.write_fmt(format_args!( + "FunctionAttributes {{ ret: {:?}, args: {:?} }}", + self.ret, self.args + )) } } + impl Debug for FunctionComponentAttributes { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!( - "ty {:?}, desc {:?}, optional {}", + "FunctionComponentAttributes {{ ty: {:?}, desc: {:?}, optional: {} }}", self.ty, self.desc, self.optional )) } diff --git a/crates/typescript-tests/run.sh b/crates/typescript-tests/run.sh index 5a3605e8300..3eb0689a1ab 100755 --- a/crates/typescript-tests/run.sh +++ b/crates/typescript-tests/run.sh @@ -48,9 +48,9 @@ for filename in *.ts; do # Copy over every line EXCEPT for the import statements (since "no-modules" has no modules to import). grep -v -w "import" "$filename" > "$path" # Then replace all of the instances of "wbg" (the namespace all the tests use to import the *.d.ts files from "--target web") with "wasm_bindgen" (the namespace the `no-modules` *.d.ts files use). - sed -i "s/wbg\./wasm_bindgen./g" "$path" - sed -i "s/wbg /wasm_bindgen /g" "$path" - sed -i "s/wbg\[/wasm_bindgen[/g" "$path" + sed -i '' "s/wbg\./wasm_bindgen./g" "$path" + sed -i '' "s/wbg /wasm_bindgen /g" "$path" + sed -i '' "s/wbg\[/wasm_bindgen[/g" "$path" fi done cd .. From 6734413e487d30302f76b760f31a3e532067d187 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 6 Jan 2025 16:02:59 +0000 Subject: [PATCH 13/50] Update run.sh --- crates/typescript-tests/run.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/typescript-tests/run.sh b/crates/typescript-tests/run.sh index 3eb0689a1ab..5a3605e8300 100755 --- a/crates/typescript-tests/run.sh +++ b/crates/typescript-tests/run.sh @@ -48,9 +48,9 @@ for filename in *.ts; do # Copy over every line EXCEPT for the import statements (since "no-modules" has no modules to import). grep -v -w "import" "$filename" > "$path" # Then replace all of the instances of "wbg" (the namespace all the tests use to import the *.d.ts files from "--target web") with "wasm_bindgen" (the namespace the `no-modules` *.d.ts files use). - sed -i '' "s/wbg\./wasm_bindgen./g" "$path" - sed -i '' "s/wbg /wasm_bindgen /g" "$path" - sed -i '' "s/wbg\[/wasm_bindgen[/g" "$path" + sed -i "s/wbg\./wasm_bindgen./g" "$path" + sed -i "s/wbg /wasm_bindgen /g" "$path" + sed -i "s/wbg\[/wasm_bindgen[/g" "$path" fi done cd .. From e0b04f913cc045adaeb2184fdbc2967ab84844a7 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Tue, 7 Jan 2025 17:47:12 +0000 Subject: [PATCH 14/50] update - rm "optional" attr - separate js/ts doc by adding ts_doc_comment() - fix docs --- crates/backend/src/ast.rs | 2 - crates/backend/src/encode.rs | 2 - crates/cli-support/src/decode.rs | 4 +- crates/cli-support/src/js/binding.rs | 192 +++++++++--------- crates/cli-support/src/wit/nonstandard.rs | 1 - .../cli/tests/reference/function-attrs.d.ts | 6 +- crates/cli/tests/reference/function-attrs.rs | 7 +- crates/macro-support/src/parser.rs | 23 --- .../ui-tests/optional-function-arg-attr.rs | 14 -- .../optional-function-arg-attr.stderr | 5 - crates/shared/src/lib.rs | 1 - crates/shared/src/schema_hash_approval.rs | 2 +- crates/typescript-tests/run.sh | 6 +- crates/typescript-tests/src/function_attrs.rs | 8 +- crates/typescript-tests/src/function_attrs.ts | 18 +- .../on-rust-exports/function-attributes.md | 48 +++-- 16 files changed, 145 insertions(+), 194 deletions(-) delete mode 100644 crates/macro/ui-tests/optional-function-arg-attr.rs delete mode 100644 crates/macro/ui-tests/optional-function-arg-attr.stderr diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 34c5c760333..bb05d07b43e 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -416,8 +416,6 @@ pub struct FunctionComponentAttributes { pub desc: Option, /// Specifies a name of the function argument pub name: Option, - /// Specifies if the component is optional (used for function arguments) - pub optional: bool, } /// Information about a Struct being exported diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index 590bdb1c04b..765898c8567 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -238,7 +238,6 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi ret: FunctionComponentAttributes { ty: attrs.ret.ty.clone(), desc: attrs.ret.desc.clone(), - optional: false, }, args: attrs .args @@ -246,7 +245,6 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi .map(|arg_attr| FunctionComponentAttributes { ty: arg_attr.ty.clone(), desc: arg_attr.desc.clone(), - optional: arg_attr.optional, }) .collect::>(), }); diff --git a/crates/cli-support/src/decode.rs b/crates/cli-support/src/decode.rs index da84b162d86..dcb3680d831 100644 --- a/crates/cli-support/src/decode.rs +++ b/crates/cli-support/src/decode.rs @@ -106,8 +106,8 @@ impl Debug for FunctionAttributes { impl Debug for FunctionComponentAttributes { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!( - "FunctionComponentAttributes {{ ty: {:?}, desc: {:?}, optional: {} }}", - self.ty, self.desc, self.optional + "FunctionComponentAttributes {{ ty: {:?}, desc: {:?} }}", + self.ty, self.desc )) } } diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 1082ac817d8..9a5eba713a7 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -278,13 +278,12 @@ impl<'a, 'b> Builder<'a, 'b> { export_fn_attrs, ); let js_doc = if generate_jsdoc { - self.js_ts_doc_comments( + self.js_doc_comments( &function_args, &arg_tys, &ts_ret_ty, variadic, export_fn_attrs, - false, ) } else { String::new() @@ -296,14 +295,7 @@ impl<'a, 'b> Builder<'a, 'b> { // casings for arguments names such as "@param {string} [arg]" that // tags the argument as optional, for ts doc we only need arg names // and rest are just derived from function ts signature - let ts_doc = self.js_ts_doc_comments( - &function_args, - &arg_tys, - &ts_ret_ty, - variadic, - export_fn_attrs, - true, - ); + let ts_doc = self.ts_doc_comments(&function_args, variadic, export_fn_attrs); Ok(JsFunction { code, @@ -337,40 +329,25 @@ impl<'a, 'b> Builder<'a, 'b> { // flatten args types overrides let args_overrides = export_fn_attrs .as_ref() - .map(|v| { - v.args - .iter() - .map(|e| (e.ty.as_ref(), e.optional)) - .collect::>() - }) - .unwrap_or(vec![(None, false); arg_names.len()]); + .map(|v| v.args.iter().map(|e| e.ty.as_ref()).collect::>()) + .unwrap_or(vec![None; arg_names.len()]); // Build up the typescript signature as well let mut omittable = true; let mut ts_args = Vec::new(); let mut ts_arg_tys = Vec::new(); let mut ts_refs = HashSet::new(); - for ((name, ty), (ty_override, optional)) in - arg_names.iter().zip(arg_tys).zip(args_overrides).rev() - { + for ((name, ty), ty_override) in arg_names.iter().zip(arg_tys).zip(args_overrides).rev() { // In TypeScript, we can mark optional parameters as omittable // using the `?` suffix, but only if they're not followed by // non-omittable parameters. Therefore iterate the parameter list // in reverse and stop using the `?` suffix for optional params as // soon as a non-optional parameter is encountered. - // - // "optional" attr already enforces this rule for all override - // attrs so we dont need to do the omittable check for args tagged - // with "optional" attr let mut arg = name.to_string(); let mut ts = String::new(); if let Some(v) = ty_override { - if optional { - arg.push_str("?: "); - } else { - omittable = false; - arg.push_str(": "); - } + omittable = false; + arg.push_str(": "); ts.push_str(v); } else { match ty { @@ -382,12 +359,8 @@ impl<'a, 'b> Builder<'a, 'b> { } ty => { adapter2ts(ty, TypePosition::Argument, &mut ts, Some(&mut ts_refs)); - if optional { - arg.push_str("?: "); - } else { - omittable = false; - arg.push_str(": "); - } + omittable = false; + arg.push_str(": "); } } } @@ -446,16 +419,15 @@ impl<'a, 'b> Builder<'a, 'b> { (ts, ts_arg_tys, ts_ret, ts_refs) } - /// Returns a helpful JS/TS doc comment which lists types for all parameters + /// Returns a helpful JS doc comment which lists types for all parameters /// and the return value. - fn js_ts_doc_comments( + fn js_doc_comments( &self, arg_names: &[String], arg_tys: &[&AdapterType], ts_ret: &Option, variadic: bool, export_fn_attrs: &Option, - is_ts: bool, ) -> String { // flatten args attributes overrides let args_overrides = &export_fn_attrs @@ -463,10 +435,10 @@ impl<'a, 'b> Builder<'a, 'b> { .map(|v| { v.args .iter() - .map(|e| (e.ty.as_ref(), e.desc.as_ref(), e.optional)) + .map(|e| (e.ty.as_ref(), e.desc.as_ref())) .collect::>() }) - .unwrap_or(vec![(None, None, false); arg_names.len()]); + .unwrap_or(vec![(None, None); arg_names.len()]); let (variadic_arg, fn_arg_names) = match arg_names.split_last() { Some((last, args)) if variadic => (Some(last), args), @@ -476,29 +448,19 @@ impl<'a, 'b> Builder<'a, 'b> { let mut omittable = true; let mut js_doc_args = Vec::new(); - for ((name, ty), (ty_override, desc, optional)) in + for ((name, ty), (ty_override, desc)) in fn_arg_names.iter().zip(arg_tys).zip(args_overrides).rev() { - let mut arg = "@param ".to_string(); - if is_ts { - // we dont need arg type for ts doc, only arg name - arg.push_str(name); - } else if let Some(v) = ty_override { - arg.push('{'); + let mut arg = "@param {".to_string(); + + if let Some(v) = ty_override { + omittable = false; arg.push_str(v); arg.push_str("} "); - if *optional { - arg.push('['); - arg.push_str(name); - arg.push(']'); - } else { - omittable = false; - arg.push_str(name); - } + arg.push_str(name); } else { match ty { AdapterType::Option(ty) if omittable => { - arg.push('{'); adapter2ts(ty, TypePosition::Argument, &mut arg, None); arg.push_str(" | null} "); arg.push('['); @@ -506,21 +468,14 @@ impl<'a, 'b> Builder<'a, 'b> { arg.push(']'); } _ => { - arg.push('{'); + omittable = false; adapter2ts(ty, TypePosition::Argument, &mut arg, None); - if *optional { - arg.push_str("} "); - arg.push('['); - arg.push_str(name); - arg.push(']'); - } else { - omittable = false; - arg.push_str("} "); - arg.push_str(name); - } + arg.push_str("} "); + arg.push_str(name); } } } + // append description if let Some(v) = desc { arg.push_str(" - "); arg.push_str(v); @@ -531,23 +486,19 @@ impl<'a, 'b> Builder<'a, 'b> { let mut ret: String = js_doc_args.into_iter().rev().collect(); - if let (Some(name), Some(ty), Some((ty_override, desc, _))) = + if let (Some(name), Some(ty), Some((ty_override, desc))) = (variadic_arg, arg_tys.last(), args_overrides.last()) { - ret.push_str("@param "); - if is_ts { - // we dont need arg type for ts doc, so only include arg name - ret.push_str(name); + ret.push_str("@param {..."); + if let Some(v) = ty_override { + ret.push_str(v); } else { - ret.push_str("{..."); - if let Some(v) = ty_override { - ret.push_str(v); - } else { - adapter2ts(ty, TypePosition::Argument, &mut ret, None); - } - ret.push_str("} "); - ret.push_str(name); + adapter2ts(ty, TypePosition::Argument, &mut ret, None); } + ret.push_str("} "); + ret.push_str(name); + + // append desc if let Some(v) = desc { ret.push_str(" - "); ret.push_str(v); @@ -561,21 +512,74 @@ impl<'a, 'b> Builder<'a, 'b> { if let Some(ts) = ret_ty_override.or(ts_ret.as_ref()) { // skip if type is void and there is no description if ts != "void" || ret_desc.is_some() { - if is_ts { - // only if there is return description as we dont want empty @return tag - if ret_desc.is_some() { - ret.push_str("@returns"); - } - } else { - ret.push_str(&format!("@returns {{{}}}", ts)); - } + ret.push_str(&format!("@returns {{{}}}", ts)); + } + // append return description + if let Some(v) = ret_desc { + ret.push(' '); + ret.push_str(v); + } + } + ret + } - // append return description - if let Some(v) = ret_desc { - ret.push(' '); - ret.push_str(v); - } + /// Returns a helpful TS doc comment which lists all parameters and + /// the return value descriptions. + fn ts_doc_comments( + &self, + arg_names: &[String], + variadic: bool, + export_fn_attrs: &Option, + ) -> String { + // flatten args desc + let args_desc = &export_fn_attrs + .as_ref() + .map(|v| v.args.iter().map(|e| e.desc.as_ref()).collect::>()) + .unwrap_or(vec![None; arg_names.len()]); + + let (variadic_arg, fn_arg_names) = match arg_names.split_last() { + Some((last, args)) if variadic => (Some(last), args), + _ => (None, arg_names), + }; + + let mut ts_doc_args = Vec::new(); + // ofc we dont need arg type for ts doc, only arg name + for (name, desc) in fn_arg_names.iter().zip(args_desc).rev() { + let mut arg = "@param ".to_string(); + arg.push_str(name); + + // append desc + if let Some(v) = desc { + arg.push_str(" - "); + arg.push_str(v); + } + + arg.push('\n'); + ts_doc_args.push(arg); + } + + let mut ret: String = ts_doc_args.into_iter().rev().collect(); + + if let (Some(name), Some(desc)) = (variadic_arg, args_desc.last()) { + ret.push_str("@param "); + ret.push_str(name); + + // append desc + if let Some(v) = desc { + ret.push_str(" - "); + ret.push_str(v); } + ret.push('\n'); + } + + // only if there is return description as we dont want empty @return tag + if let Some(ret_desc) = export_fn_attrs + .as_ref() + .map(|v| v.ret.desc.as_ref()) + .unwrap_or(None) + { + ret.push_str("@returns "); + ret.push_str(ret_desc); } ret } diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index dac7400d922..9f06e421f2c 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -3,7 +3,6 @@ use crate::intrinsic::Intrinsic; use crate::wit::AdapterId; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; -use std::fmt::Debug; use std::path::PathBuf; use walrus::TypedCustomSectionId; diff --git a/crates/cli/tests/reference/function-attrs.d.ts b/crates/cli/tests/reference/function-attrs.d.ts index f33b6414fac..7ae18c6a5c9 100644 --- a/crates/cli/tests/reference/function-attrs.d.ts +++ b/crates/cli/tests/reference/function-attrs.d.ts @@ -6,7 +6,7 @@ * @param secondArg * @returns returns 1 if arg2 is true, or arg1 if arg2 is undefined or false */ -export function fn_with_attr(firstArg: number, secondArg?: boolean): Promise; +export function fn_with_attr(firstArg: number, secondArg: boolean | undefined): Promise; /** * Description for HoldsNumber */ @@ -19,14 +19,14 @@ export class HoldsNumber { * @param secondArg * @returns returns an instance of HoldsNumber, holding arg1 if arg2 is undefined and holding arg2 if not */ - static static_fn_with_attr(firstArg: number, secondArg?: number): HoldsNumber; + static static_fn_with_attr(firstArg: number, secondArg: number | undefined): HoldsNumber; /** * Description for method_with_attr * @param firstArg - some number * @param secondArg * @returns returns arg1 if arg2 is true, or holding value of self if arg2 is undefined or false */ - method_with_attr(firstArg: number, secondArg?: boolean): number; + method_with_attr(firstArg: number, secondArg: boolean | undefined): number; /** * Inner value */ diff --git a/crates/cli/tests/reference/function-attrs.rs b/crates/cli/tests/reference/function-attrs.rs index 7de458a8a6f..64e60047310 100644 --- a/crates/cli/tests/reference/function-attrs.rs +++ b/crates/cli/tests/reference/function-attrs.rs @@ -7,7 +7,7 @@ use wasm_bindgen::prelude::*; )] pub async fn fn_with_attr( #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "boolean", optional)] arg2: JsValue, + #[wasm_bindgen(js_name = "secondArg", param_type = "boolean | undefined")] arg2: JsValue, ) -> Result { if arg2.is_undefined() { Ok(arg1.into()) @@ -38,8 +38,7 @@ impl HoldsNumber { )] pub fn static_fn_with_attr( #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "number")] - #[wasm_bindgen(optional)] + #[wasm_bindgen(js_name = "secondArg", param_type = "number | undefined")] arg2: JsValue, ) -> HoldsNumber { if arg2.is_undefined() { @@ -57,7 +56,7 @@ impl HoldsNumber { pub fn method_with_attr( &self, #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "boolean", optional)] arg2: JsValue, + #[wasm_bindgen(js_name = "secondArg", param_type = "boolean | undefined")] arg2: JsValue, ) -> JsValue { if arg2.is_undefined() { self.inner.clone() diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index b22d1813eee..64b515234f1 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -1177,7 +1177,6 @@ fn extract_fn_attrs( ty: None, desc: None, name: None, - optional: false, }; for attr in pat_type.attrs.iter() { if !attr.path().is_ident("wasm_bindgen") { @@ -1215,32 +1214,11 @@ fn extract_fn_attrs( arg_attrs.desc = Some(meta.value()?.parse::()?.value()); return Ok(()); } - if meta.path.is_ident("optional") { - if arg_attrs.optional { - return Err(meta.error("duplicate attribute")); - } - arg_attrs.optional = true; - return Ok(()); - } Err(meta.error("unrecognized wasm_bindgen param attribute, expected any of 'param_type', 'param_description', 'js_name' or 'optional'")) })?; } - // in ts, non-optional args cannot come after an optional one - // this enforces the correct and valid use of "optional" attr - if !arg_attrs.optional - && args_attrs - .last() - .map(|v: &FunctionComponentAttributes| v.optional) - .unwrap_or(false) - { - bail_span!( - pat_type, - "non-optional arguments cannot be followed after an optional one" - ) - } - // remove extracted attributes let mut keep = keep.iter(); pat_type.attrs.retain(|_| *keep.next().unwrap()); @@ -1255,7 +1233,6 @@ fn extract_fn_attrs( ty: attrs.return_type().map(|v| v.0.to_string()), desc: attrs.return_description().map(|v| v.0.to_string()), name: None, - optional: false, }, }) } diff --git a/crates/macro/ui-tests/optional-function-arg-attr.rs b/crates/macro/ui-tests/optional-function-arg-attr.rs deleted file mode 100644 index 8d979e2cd01..00000000000 --- a/crates/macro/ui-tests/optional-function-arg-attr.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![deny(unused_variables)] - -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -pub async fn fn_with_attr( - a: u32, - #[wasm_bindgen(optional)] b: JsValue, - c: bool, -) -> Result<(), JsValue> { - Ok(()) -} - -fn main() {} diff --git a/crates/macro/ui-tests/optional-function-arg-attr.stderr b/crates/macro/ui-tests/optional-function-arg-attr.stderr deleted file mode 100644 index 6bf7651485f..00000000000 --- a/crates/macro/ui-tests/optional-function-arg-attr.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: non-optional arguments cannot be followed after an optional one - --> ui-tests/optional-function-arg-attr.rs:9:5 - | -9 | c: bool, - | ^^^^^^^ diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index d30b031504f..a032005ee09 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -152,7 +152,6 @@ macro_rules! shared_api { struct FunctionComponentAttributes { ty: Option, desc: Option, - optional: bool, } struct Struct<'a> { diff --git a/crates/shared/src/schema_hash_approval.rs b/crates/shared/src/schema_hash_approval.rs index 770b91bcd85..9836a17fef7 100644 --- a/crates/shared/src/schema_hash_approval.rs +++ b/crates/shared/src/schema_hash_approval.rs @@ -8,7 +8,7 @@ // If the schema in this library has changed then: // 1. Bump the version in `crates/shared/Cargo.toml` // 2. Change the `SCHEMA_VERSION` in this library to this new Cargo.toml version -const APPROVED_SCHEMA_FILE_HASH: &str = "11919406220845350687"; +const APPROVED_SCHEMA_FILE_HASH: &str = "10466730773526925892"; #[test] fn schema_version() { diff --git a/crates/typescript-tests/run.sh b/crates/typescript-tests/run.sh index 5a3605e8300..3eb0689a1ab 100755 --- a/crates/typescript-tests/run.sh +++ b/crates/typescript-tests/run.sh @@ -48,9 +48,9 @@ for filename in *.ts; do # Copy over every line EXCEPT for the import statements (since "no-modules" has no modules to import). grep -v -w "import" "$filename" > "$path" # Then replace all of the instances of "wbg" (the namespace all the tests use to import the *.d.ts files from "--target web") with "wasm_bindgen" (the namespace the `no-modules` *.d.ts files use). - sed -i "s/wbg\./wasm_bindgen./g" "$path" - sed -i "s/wbg /wasm_bindgen /g" "$path" - sed -i "s/wbg\[/wasm_bindgen[/g" "$path" + sed -i '' "s/wbg\./wasm_bindgen./g" "$path" + sed -i '' "s/wbg /wasm_bindgen /g" "$path" + sed -i '' "s/wbg\[/wasm_bindgen[/g" "$path" fi done cd .. diff --git a/crates/typescript-tests/src/function_attrs.rs b/crates/typescript-tests/src/function_attrs.rs index 7de458a8a6f..8a19c9415da 100644 --- a/crates/typescript-tests/src/function_attrs.rs +++ b/crates/typescript-tests/src/function_attrs.rs @@ -7,7 +7,7 @@ use wasm_bindgen::prelude::*; )] pub async fn fn_with_attr( #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "boolean", optional)] arg2: JsValue, + #[wasm_bindgen(js_name = "secondArg", param_type = "boolean | undefined")] arg2: JsValue, ) -> Result { if arg2.is_undefined() { Ok(arg1.into()) @@ -38,9 +38,7 @@ impl HoldsNumber { )] pub fn static_fn_with_attr( #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "number")] - #[wasm_bindgen(optional)] - arg2: JsValue, + #[wasm_bindgen(js_name = "secondArg", param_type = "number | undefined")] arg2: JsValue, ) -> HoldsNumber { if arg2.is_undefined() { HoldsNumber { inner: arg1.into() } @@ -57,7 +55,7 @@ impl HoldsNumber { pub fn method_with_attr( &self, #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "boolean", optional)] arg2: JsValue, + #[wasm_bindgen(js_name = "secondArg", param_type = "boolean | undefined")] arg2: JsValue, ) -> JsValue { if arg2.is_undefined() { self.inner.clone() diff --git a/crates/typescript-tests/src/function_attrs.ts b/crates/typescript-tests/src/function_attrs.ts index c85e18bf98a..287edd2fbf7 100644 --- a/crates/typescript-tests/src/function_attrs.ts +++ b/crates/typescript-tests/src/function_attrs.ts @@ -2,15 +2,15 @@ import * as wbg from '../pkg/typescript_tests'; import * as wasm from '../pkg/typescript_tests_bg.wasm'; import { expect, test } from "@jest/globals"; -const wasm_fn_with_attr: (a: number, b?: number) => number = wasm.fn_with_attr; -const wbg_fn_with_attr: (a: number, b?: boolean) => Promise = wbg.fn_with_attr; -const wasm_holdsnumber_static_fn_with_attr: (a: number, b?: number) => number = wasm.holdsnumber_static_fn_with_attr; -const wbg_holdsnumber_static_fn_with_attr: (a1: number, b?: number) => wbg.HoldsNumber = wbg.HoldsNumber.static_fn_with_attr; +const wasm_fn_with_attr: (a: number, b: number) => number = wasm.fn_with_attr; +const wbg_fn_with_attr: (a: number, b: boolean) => Promise = wbg.fn_with_attr; +const wasm_holdsnumber_static_fn_with_attr: (a: number, b: number) => number = wasm.holdsnumber_static_fn_with_attr; +const wbg_holdsnumber_static_fn_with_attr: (a1: number, b: number) => wbg.HoldsNumber = wbg.HoldsNumber.static_fn_with_attr; const wasm_holdsnumber_method_with_attr: (a: number, b: number, c: number) => number = wasm.holdsnumber_method_with_attr; -const wbg_holdsnumber_method_with_attr: (a: number, b?: boolean) => number = wbg.HoldsNumber.static_fn_with_attr(1).method_with_attr; +const wbg_holdsnumber_method_with_attr: (a: number, b: boolean) => number = wbg.HoldsNumber.static_fn_with_attr(1, undefined).method_with_attr; test("async function fn_with_attr", async () => { - let result = await wbg.fn_with_attr(4); + let result = await wbg.fn_with_attr(4, undefined); expect(result).toEqual(4); result = await wbg.fn_with_attr(5, false); @@ -21,12 +21,12 @@ test("async function fn_with_attr", async () => { }); test("HoldsNumber methods", () => { - const num1 = wbg.HoldsNumber.static_fn_with_attr(4).inner; + const num1 = wbg.HoldsNumber.static_fn_with_attr(4, undefined).inner; const num2 = wbg.HoldsNumber.static_fn_with_attr(3, 4).inner; expect(num1).toEqual(num2); - const holdsNumber = wbg.HoldsNumber.static_fn_with_attr(8); - let result = holdsNumber.method_with_attr(4); + const holdsNumber = wbg.HoldsNumber.static_fn_with_attr(8, undefined); + let result = holdsNumber.method_with_attr(4, undefined); expect(result).toEqual(8); result = holdsNumber.method_with_attr(5, false); diff --git a/guide/src/reference/attributes/on-rust-exports/function-attributes.md b/guide/src/reference/attributes/on-rust-exports/function-attributes.md index d5038aa8ca1..1bf998ee9c5 100644 --- a/guide/src/reference/attributes/on-rust-exports/function-attributes.md +++ b/guide/src/reference/attributes/on-rust-exports/function-attributes.md @@ -1,14 +1,10 @@ # `function-attributes` -By default, exported Rust functions and methods generate function signature from equivalent rust types identifiers without any arguments and return var documentations unless completely handcoded using `skip-jsdoc` and `typescript-custom-section` with `skip_typescript` which does not provide the best solution, specially when one needs to do a lot of those handcodings. -It's fair to say that being able to write specific documentation for each argument and return variable of the function, or override an argument or return variable type to a custom one for generated js/ts bindings and many more use cases are essential for creating a well defined, structured and typed bindings. +By default, exported Rust functions and methods generate function signature from equivalent Rust types identifiers without any arguments and return var documentations unless completely handcoded using `skip-jsdoc` and `typescript-custom-section` with `skip_typescript`, however by using function attributes, it's possible to override a function's return type and arguments names and types for generated bindings as well as the ability to write specific documentation for each of them individually as desired: +- `#[wasm_bindgen(return_type)]` and `#[wasm_bindgen(return_description)]` used to override a function's return type and to specify descriptions for generated JS/TS bindings. +- `#[wasm_bindgen(js_name)]`, `#[wasm_bindgen(param_type)]` and `#[wasm_bindgen(param_description)]` applied to a Rust function argument to override that argument's name and type and to specify descriptions for generated JS/TS bindings. -Function attributes addresses these issues and limitations by providing an ability to override a function's return type and arguments names and types for generated bindings as well as ability to write specific documentation for each of them individually as desired: -- `#[wasm_bindgen(return_type)]` and `#[wasm_bindgen(return_description)]` used to override function's return type and to specify description for generated js/ts bindings. -- `#[wasm_bindgen(js_name)]`, `#[wasm_bindgen(param_type)]` and `#[wasm_bindgen(param_description)]` applied to a rust function argument to override that argument's name and type and to specify description for generated js/ts bindings. -- `#[wasm_bindgen(optional)]` used to tag a function's argument as optional (typescript `?` operator) for function's generated typescript signature. - -For example a rust function can return `JsValue` by serializing a rust type using serde, yet on generated ts bindings instead of `any` as the return type, it can be overriden to the ts interface of the serialized rust type equivalent defined using `typescript-custom-section` (or using [Tsify Crate](https://crates.io/crates/tsify)): +For example a Rust function can return `JsValue` by serializing a Rust type using serde, yet on generated TS bindings instead of `any` as the return type, it can be overridden to the TS interface of the serialized Rust type equivalent defined using `typescript-custom-section`: ```rust // we wont use "#[wasm_bindgen]" for this struct #[derive(serde::Serialize, serde::Deserialize)] @@ -16,11 +12,10 @@ pub struct Person { pub name: String; } -// define a ts interface equivalent of Person struct -// alternatively Tsify crate can be used to generate ts interface from rust types +// define a TS interface equivalent of Person struct #[wasm_bindgen(typescript_custom_section)] const TS_INTERFACE_EXPORT: &'static str = r" - export interface Person { name: string; } +export interface Person { name: string; } "; #[wasm_bindgen(return_type = "Person", return_description = "a Person object")] @@ -34,7 +29,7 @@ pub async fn build_person( Ok(serde_wasm_bindgen::to_value(&Person { name: person_name })?) } ``` -this will generate the following ts bindings: +this will generate the following TS bindings: ```ts export interface Person { name: string; } @@ -44,7 +39,7 @@ export interface Person { name: string; } */ export function build_person(personName: string): Promise; ``` -As you can see, using function attributes, we can now return a js/ts object without ever using `wasm_bindgen` macro on `Person` struct that would have generated a js/ts class object in the bindings which might not be the desired outcome for every case, you can see as well that instead of handcoding the full documentation for `build_person()` with all js_doc/ts_doc tags and syntax, we can just write specific docs for each individual component of the function and as a result end up with a fully typed and documented function in the bindings. +As you can see, using function attributes, we can now return a JS/TS object without ever using `wasm_bindgen` macro on `Person` struct that would have generated a JS/TS class object in the bindings which might not be the desired outcome for every case, you can see as well that instead of handcoding the full documentation for `build_person()` with all jsDoc/tsDoc tags and syntax, we can just write specific docs for each individual component of the function and as a result end up with a fully typed and documented function in the bindings. Let's look at some more exmaples in details: ```rust @@ -53,23 +48,23 @@ Let's look at some more exmaples in details: pub async fn foo( #[wasm_bindgen(js_name = "firstArg", param_description = "some description for firstArg")] arg1: String, - #[wasm_bindgen(js_name = "secondArg", param_type = "Bar", optional)] + #[wasm_bindgen(js_name = "secondArg", param_type = "Bar")] arg2: JsValue, ) -> Result { // function body } ``` -This will generate the following js bindings: +This will generate the following JS bindings: ```js /** * Description for foo * @param {string} firstArg - some description for firstArg -* @param {Bar} [secondArg] +* @param {Bar} secondArg * @returns {Promise} some description for return type */ export function foo(firstArg, secondArg) {}; ``` -And will generate the following ts bindings: +And will generate the following TS bindings: ```ts /** * Description for foo @@ -77,10 +72,10 @@ And will generate the following ts bindings: * @param secondArg * @returns some description for return type */ -export function foo(firstArg: string, secondArg?: Bar): Promise; +export function foo(firstArg: string, secondArg: Bar): Promise; ``` -Same thing applies to rust struct's (and enums) impl methods and their equivalent js/ts class methods: +Same thing applies to Rust struct's (and enums) impl methods and their equivalent JS/TS/TS class methods: ```rust /// Description for Foo #[wasm_bindgen] @@ -96,7 +91,7 @@ impl Foo { &self, #[wasm_bindgen(js_name = "firstArg", param_description = "some description for firstArg")] arg1: String, - #[wasm_bindgen(js_name = "secondArg", param_type = "Bar", optional)] + #[wasm_bindgen(js_name = "secondArg", param_type = "Bar")] arg2: JsValue, ) -> JsValue { // function body @@ -104,7 +99,7 @@ impl Foo { } ``` -This will generate the following js bindings: +This will generate the following JS bindings: ```js /** * Description for Foo @@ -113,14 +108,14 @@ export class Foo { /** * Description for foo * @param {string} firstArg - some description for firstArg - * @param {Bar} [secondArg] + * @param {Bar} secondArg * @returns {Baz} some description for return type */ foo(firstArg, secondArg) {}; } ``` -And will generate the following ts bindings: +And will generate the following TS bindings: ```ts /** * Description for Foo @@ -132,8 +127,11 @@ export class Foo { * @param secondArg * @returns some description for return type */ - foo(firstArg: string, secondArg?: Bar): Baz; + foo(firstArg: string, secondArg: Bar): Baz; } ``` -As shown in exmaples, these attributes allows for great level of control and customization over generated bindings but note that they can only be used on functions and methods that are being exported to js/ts and cannot be used on `self` argument of rust structs/enums methods. \ No newline at end of file +As shown in these examples, these attributes allows for a great level of control and customization over generated bindings. But note that they can only be used on functions and methods that are being exported to JS/TS/TS and cannot be used on the `self` argument of Rust struct/enum methods. + +**IMPORTANT NOTE** +Types that are provided using `#[wasm_bindgen(return_type)]` and `#[wasm_bindgen(param_type)]` aren't checked, meaning they will end up in the bindings as they have been specified and there are no checks for them in place, so only because a user uses '#[wasm_bindgen(param_type = "number")]', it doesn't mean its actually going to be a number type we are expecting here, so the responsibility of using these attributes and making sure that they are used correctly and carefully relies solely on the user. \ No newline at end of file From 6e7e1d2160b272321701763ffcd67ba3ffa425b2 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Tue, 7 Jan 2025 18:10:06 +0000 Subject: [PATCH 15/50] fix ts doc generator fn --- crates/cli-support/src/js/binding.rs | 24 +++--------------------- crates/typescript-tests/run.sh | 6 +++--- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 9a5eba713a7..112f3afa86d 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -295,7 +295,7 @@ impl<'a, 'b> Builder<'a, 'b> { // casings for arguments names such as "@param {string} [arg]" that // tags the argument as optional, for ts doc we only need arg names // and rest are just derived from function ts signature - let ts_doc = self.ts_doc_comments(&function_args, variadic, export_fn_attrs); + let ts_doc = self.ts_doc_comments(&function_args, export_fn_attrs); Ok(JsFunction { code, @@ -528,7 +528,6 @@ impl<'a, 'b> Builder<'a, 'b> { fn ts_doc_comments( &self, arg_names: &[String], - variadic: bool, export_fn_attrs: &Option, ) -> String { // flatten args desc @@ -537,14 +536,9 @@ impl<'a, 'b> Builder<'a, 'b> { .map(|v| v.args.iter().map(|e| e.desc.as_ref()).collect::>()) .unwrap_or(vec![None; arg_names.len()]); - let (variadic_arg, fn_arg_names) = match arg_names.split_last() { - Some((last, args)) if variadic => (Some(last), args), - _ => (None, arg_names), - }; - let mut ts_doc_args = Vec::new(); // ofc we dont need arg type for ts doc, only arg name - for (name, desc) in fn_arg_names.iter().zip(args_desc).rev() { + for (name, desc) in arg_names.iter().zip(args_desc) { let mut arg = "@param ".to_string(); arg.push_str(name); @@ -558,19 +552,7 @@ impl<'a, 'b> Builder<'a, 'b> { ts_doc_args.push(arg); } - let mut ret: String = ts_doc_args.into_iter().rev().collect(); - - if let (Some(name), Some(desc)) = (variadic_arg, args_desc.last()) { - ret.push_str("@param "); - ret.push_str(name); - - // append desc - if let Some(v) = desc { - ret.push_str(" - "); - ret.push_str(v); - } - ret.push('\n'); - } + let mut ret: String = ts_doc_args.into_iter().collect(); // only if there is return description as we dont want empty @return tag if let Some(ret_desc) = export_fn_attrs diff --git a/crates/typescript-tests/run.sh b/crates/typescript-tests/run.sh index 3eb0689a1ab..5a3605e8300 100755 --- a/crates/typescript-tests/run.sh +++ b/crates/typescript-tests/run.sh @@ -48,9 +48,9 @@ for filename in *.ts; do # Copy over every line EXCEPT for the import statements (since "no-modules" has no modules to import). grep -v -w "import" "$filename" > "$path" # Then replace all of the instances of "wbg" (the namespace all the tests use to import the *.d.ts files from "--target web") with "wasm_bindgen" (the namespace the `no-modules` *.d.ts files use). - sed -i '' "s/wbg\./wasm_bindgen./g" "$path" - sed -i '' "s/wbg /wasm_bindgen /g" "$path" - sed -i '' "s/wbg\[/wasm_bindgen[/g" "$path" + sed -i "s/wbg\./wasm_bindgen./g" "$path" + sed -i "s/wbg /wasm_bindgen /g" "$path" + sed -i "s/wbg\[/wasm_bindgen[/g" "$path" fi done cd .. From f24b60580af646f5a6b151d2c7e09671f835118c Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Tue, 7 Jan 2025 23:42:45 +0000 Subject: [PATCH 16/50] update - append `unchecked` for type overrides attrs - use attrgen for parsing function attrs - update docs --- crates/cli/tests/reference/function-attrs.rs | 12 ++-- crates/macro-support/src/parser.rs | 63 +++---------------- crates/typescript-tests/src/function_attrs.rs | 15 +++-- .../on-rust-exports/function-attributes.md | 28 ++++----- 4 files changed, 39 insertions(+), 79 deletions(-) diff --git a/crates/cli/tests/reference/function-attrs.rs b/crates/cli/tests/reference/function-attrs.rs index 64e60047310..2621a4cc37d 100644 --- a/crates/cli/tests/reference/function-attrs.rs +++ b/crates/cli/tests/reference/function-attrs.rs @@ -2,12 +2,12 @@ use wasm_bindgen::prelude::*; /// Description for fn_with_attr #[wasm_bindgen( - return_type = "number", + unchecked_return_type = "number", return_description = "returns 1 if arg2 is true, or arg1 if arg2 is undefined or false" )] pub async fn fn_with_attr( #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "boolean | undefined")] arg2: JsValue, + #[wasm_bindgen(js_name = "secondArg", unchecked_param_type = "boolean | undefined")] arg2: JsValue, ) -> Result { if arg2.is_undefined() { Ok(arg1.into()) @@ -27,7 +27,7 @@ pub struct HoldsNumber { #[wasm_bindgen] impl HoldsNumber { /// Inner value - #[wasm_bindgen(getter = "inner", return_type = "number")] + #[wasm_bindgen(getter = "inner", unchecked_return_type = "number")] pub fn get_inner(&self) -> JsValue { self.inner.clone() } @@ -38,7 +38,7 @@ impl HoldsNumber { )] pub fn static_fn_with_attr( #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "number | undefined")] + #[wasm_bindgen(js_name = "secondArg", unchecked_param_type = "number | undefined")] arg2: JsValue, ) -> HoldsNumber { if arg2.is_undefined() { @@ -50,13 +50,13 @@ impl HoldsNumber { /// Description for method_with_attr #[wasm_bindgen( - return_type = "number", + unchecked_return_type = "number", return_description = "returns arg1 if arg2 is true, or holding value of self if arg2 is undefined or false" )] pub fn method_with_attr( &self, #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "boolean | undefined")] arg2: JsValue, + #[wasm_bindgen(js_name = "secondArg", unchecked_param_type = "boolean | undefined")] arg2: JsValue, ) -> JsValue { if arg2.is_undefined() { self.inner.clone() diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 64b515234f1..b6df791061f 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -166,8 +166,10 @@ macro_rules! attrgen { (static_string, StaticString(Span)), (thread_local, ThreadLocal(Span)), (thread_local_v2, ThreadLocalV2(Span)), - (return_type, ReturnType(Span, String, Span)), + (unchecked_return_type, ReturnType(Span, String, Span)), (return_description, ReturnDesc(Span, String, Span)), + (unchecked_param_type, ParamType(Span, String, Span)), + (param_description, ParamDesc(Span, String, Span)), // For testing purposes only. (assert_no_shim, AssertNoShim(Span)), @@ -1172,65 +1174,20 @@ fn extract_fn_attrs( let mut args_attrs = vec![]; for input in sig.inputs.iter_mut() { if let syn::FnArg::Typed(pat_type) = input { - let mut keep = vec![]; - let mut arg_attrs = FunctionComponentAttributes { - ty: None, - desc: None, - name: None, + let attrs = BindgenAttrs::find(&mut pat_type.attrs)?; + let arg_attrs = FunctionComponentAttributes { + ty: attrs.unchecked_param_type().map(|v| v.0.to_string()), + desc: attrs.param_description().map(|v| v.0.to_string()), + name: attrs.js_name().map(|v| v.0.to_string()), }; - for attr in pat_type.attrs.iter() { - if !attr.path().is_ident("wasm_bindgen") { - keep.push(true); - continue; - } - keep.push(false); - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("js_name") { - if arg_attrs.name.is_some() { - return Err(meta.error("duplicate attribute")); - } - let value = meta.value()?.parse::()?.value(); - if is_js_keyword(&value) { - return Err(meta.error("collides with js/ts keywords")); - } - arg_attrs.name = Some(value); - return Ok(()); - } - if meta.path.is_ident("param_type") { - if arg_attrs.ty.is_some() { - return Err(meta.error("duplicate attribute")); - } - let value = meta.value()?.parse::()?.value(); - if is_js_keyword(&value) { - return Err(meta.error("collides with js/ts keywords")); - } - arg_attrs.ty = Some(value); - return Ok(()); - } - if meta.path.is_ident("param_description") { - if arg_attrs.desc.is_some() { - return Err(meta.error("duplicate attribute")); - } - arg_attrs.desc = Some(meta.value()?.parse::()?.value()); - return Ok(()); - } - - Err(meta.error("unrecognized wasm_bindgen param attribute, expected any of 'param_type', 'param_description', 'js_name' or 'optional'")) - })?; - } - - // remove extracted attributes - let mut keep = keep.iter(); - pat_type.attrs.retain(|_| *keep.next().unwrap()); - + attrs.check_used(); args_attrs.push(arg_attrs); } } - Ok(FunctionAttributes { args: args_attrs, ret: FunctionComponentAttributes { - ty: attrs.return_type().map(|v| v.0.to_string()), + ty: attrs.unchecked_return_type().map(|v| v.0.to_string()), desc: attrs.return_description().map(|v| v.0.to_string()), name: None, }, diff --git a/crates/typescript-tests/src/function_attrs.rs b/crates/typescript-tests/src/function_attrs.rs index 8a19c9415da..824954f8cc5 100644 --- a/crates/typescript-tests/src/function_attrs.rs +++ b/crates/typescript-tests/src/function_attrs.rs @@ -2,12 +2,13 @@ use wasm_bindgen::prelude::*; /// Description for fn_with_attr #[wasm_bindgen( - return_type = "number", + unchecked_return_type = "number", return_description = "returns 1 if arg2 is true, or arg1 if arg2 is undefined or false" )] pub async fn fn_with_attr( #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "boolean | undefined")] arg2: JsValue, + #[wasm_bindgen(js_name = "secondArg", unchecked_param_type = "boolean | undefined")] + arg2: JsValue, ) -> Result { if arg2.is_undefined() { Ok(arg1.into()) @@ -27,7 +28,7 @@ pub struct HoldsNumber { #[wasm_bindgen] impl HoldsNumber { /// Inner value - #[wasm_bindgen(getter = "inner", return_type = "number")] + #[wasm_bindgen(getter = "inner", unchecked_return_type = "number")] pub fn get_inner(&self) -> JsValue { self.inner.clone() } @@ -38,7 +39,8 @@ impl HoldsNumber { )] pub fn static_fn_with_attr( #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "number | undefined")] arg2: JsValue, + #[wasm_bindgen(js_name = "secondArg", unchecked_param_type = "number | undefined")] + arg2: JsValue, ) -> HoldsNumber { if arg2.is_undefined() { HoldsNumber { inner: arg1.into() } @@ -49,13 +51,14 @@ impl HoldsNumber { /// Description for method_with_attr #[wasm_bindgen( - return_type = "number", + unchecked_return_type = "number", return_description = "returns arg1 if arg2 is true, or holding value of self if arg2 is undefined or false" )] pub fn method_with_attr( &self, #[wasm_bindgen(js_name = "firstArg", param_description = "some number")] arg1: u32, - #[wasm_bindgen(js_name = "secondArg", param_type = "boolean | undefined")] arg2: JsValue, + #[wasm_bindgen(js_name = "secondArg", unchecked_param_type = "boolean | undefined")] + arg2: JsValue, ) -> JsValue { if arg2.is_undefined() { self.inner.clone() diff --git a/guide/src/reference/attributes/on-rust-exports/function-attributes.md b/guide/src/reference/attributes/on-rust-exports/function-attributes.md index 1bf998ee9c5..6e07cd2c9f9 100644 --- a/guide/src/reference/attributes/on-rust-exports/function-attributes.md +++ b/guide/src/reference/attributes/on-rust-exports/function-attributes.md @@ -1,10 +1,10 @@ # `function-attributes` -By default, exported Rust functions and methods generate function signature from equivalent Rust types identifiers without any arguments and return var documentations unless completely handcoded using `skip-jsdoc` and `typescript-custom-section` with `skip_typescript`, however by using function attributes, it's possible to override a function's return type and arguments names and types for generated bindings as well as the ability to write specific documentation for each of them individually as desired: -- `#[wasm_bindgen(return_type)]` and `#[wasm_bindgen(return_description)]` used to override a function's return type and to specify descriptions for generated JS/TS bindings. -- `#[wasm_bindgen(js_name)]`, `#[wasm_bindgen(param_type)]` and `#[wasm_bindgen(param_description)]` applied to a Rust function argument to override that argument's name and type and to specify descriptions for generated JS/TS bindings. +By default, exported Rust functions and methods generate function signature from equivalent Rust types identifiers without any arguments and return variable documentations unless completely handcoded using `skip-jsdoc` and `typescript-custom-section` with `skip_typescript`, however by using function attributes, it's possible to override a function's return type and arguments names and types for generated bindings as well as the ability to write specific documentation for each of them individually as desired: +- `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(return_description)]` used to override a function's return type and to specify descriptions for generated JS/TS bindings. +- `#[wasm_bindgen(js_name)]`, `#[wasm_bindgen(unchecked_param_type)]` and `#[wasm_bindgen(param_description)]` applied to a Rust function argument to override that argument's name and type and to specify descriptions for generated JS/TS bindings. -For example a Rust function can return `JsValue` by serializing a Rust type using serde, yet on generated TS bindings instead of `any` as the return type, it can be overridden to the TS interface of the serialized Rust type equivalent defined using `typescript-custom-section`: +For example a Rust function can return `JsValue` by serializing a Rust type using serde, yet on generated TS bindings instead of `any` as the return type, it can be overridden to the TS interface of the equivalent of the serialized Rust type defined using `typescript-custom-section`: ```rust // we wont use "#[wasm_bindgen]" for this struct #[derive(serde::Serialize, serde::Deserialize)] @@ -18,7 +18,7 @@ const TS_INTERFACE_EXPORT: &'static str = r" export interface Person { name: string; } "; -#[wasm_bindgen(return_type = "Person", return_description = "a Person object")] +#[wasm_bindgen(unchecked_return_type = "Person", return_description = "a Person object")] pub async fn build_person( #[wasm_bindgen(js_name = "personName", param_description = "Specifies the person's name")] person_name: String, @@ -39,16 +39,19 @@ export interface Person { name: string; } */ export function build_person(personName: string): Promise; ``` -As you can see, using function attributes, we can now return a JS/TS object without ever using `wasm_bindgen` macro on `Person` struct that would have generated a JS/TS class object in the bindings which might not be the desired outcome for every case, you can see as well that instead of handcoding the full documentation for `build_person()` with all jsDoc/tsDoc tags and syntax, we can just write specific docs for each individual component of the function and as a result end up with a fully typed and documented function in the bindings. +As you can see, using function attributes, we can now return a JS/TS object without ever using `wasm_bindgen` macro on `Person` struct that would have generated a JS/TS class object in the bindings which might not be the desired outcome for every case, you can see as well that instead of handcoding the full documentation for `build_person()` with all JSDoc/TSDoc tags and syntax, we can just write specific description for each individual component of the function and as a result end up with a fully typed and documented function in the bindings. + +**IMPORTANT NOTE** +Types that are provided using `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(unchecked_param_type)]` aren't checked as these attributes' identifiers entail, meaning they will end up in the function's signature and docs bindings exactly as they have been specified and there are no checks/validation for them in place, so only because a user uses '#[wasm_bindgen(unchecked_param_type = "number")]' for example, it doesn't necessarily mean it's actually going to be a value of number type, therefore validation and checks between the value and its type should be handled by the user and the responsibility of using them correctly and carefully relies solely on the user. Let's look at some more exmaples in details: ```rust /// Description for foo -#[wasm_bindgen(return_type = "Foo", return_description = "some description for return type")] +#[wasm_bindgen(unchecked_return_type = "Foo", return_description = "some description for return type")] pub async fn foo( #[wasm_bindgen(js_name = "firstArg", param_description = "some description for firstArg")] arg1: String, - #[wasm_bindgen(js_name = "secondArg", param_type = "Bar")] + #[wasm_bindgen(js_name = "secondArg", unchecked_param_type = "Bar")] arg2: JsValue, ) -> Result { // function body @@ -80,18 +83,18 @@ Same thing applies to Rust struct's (and enums) impl methods and their equivalen /// Description for Foo #[wasm_bindgen] pub struct Foo { - pub Foo: String, + // properties } #[wasm_bindgen] impl Foo { /// Description for foo - #[wasm_bindgen(return_type = "Baz", return_description = "some description for return type")] + #[wasm_bindgen(unchecked_return_type = "Baz", return_description = "some description for return type")] pub fn foo( &self, #[wasm_bindgen(js_name = "firstArg", param_description = "some description for firstArg")] arg1: String, - #[wasm_bindgen(js_name = "secondArg", param_type = "Bar")] + #[wasm_bindgen(js_name = "secondArg", unchecked_param_type = "Bar")] arg2: JsValue, ) -> JsValue { // function body @@ -132,6 +135,3 @@ export class Foo { ``` As shown in these examples, these attributes allows for a great level of control and customization over generated bindings. But note that they can only be used on functions and methods that are being exported to JS/TS/TS and cannot be used on the `self` argument of Rust struct/enum methods. - -**IMPORTANT NOTE** -Types that are provided using `#[wasm_bindgen(return_type)]` and `#[wasm_bindgen(param_type)]` aren't checked, meaning they will end up in the bindings as they have been specified and there are no checks for them in place, so only because a user uses '#[wasm_bindgen(param_type = "number")]', it doesn't mean its actually going to be a number type we are expecting here, so the responsibility of using these attributes and making sure that they are used correctly and carefully relies solely on the user. \ No newline at end of file From f3a6ba0a85d7fc17176a0488bb20dcfb4c6b0701 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 8 Jan 2025 01:32:19 +0000 Subject: [PATCH 17/50] fold fn attrs inside original props --- crates/backend/src/ast.rs | 40 ++++----- crates/backend/src/codegen.rs | 16 ++-- crates/backend/src/encode.rs | 22 +++-- crates/macro-support/src/parser.rs | 81 +++++++++++-------- crates/macro/ui-tests/unused-attributes.rs | 8 +- .../macro/ui-tests/unused-attributes.stderr | 48 +++++------ 6 files changed, 113 insertions(+), 102 deletions(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index bb05d07b43e..21edf513c8a 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -375,9 +375,9 @@ pub struct Function { /// Whether the function has a js_name attribute pub renamed_via_js_name: bool, /// The arguments to the function - pub arguments: Vec, + pub arguments: Vec, /// The return type of the function, if provided - pub ret: Option, + pub ret: FunctionReturnData, /// Any custom attributes being applied to the function pub rust_attrs: Vec, /// The visibility of this function in Rust @@ -392,30 +392,32 @@ pub struct Function { pub generate_jsdoc: bool, /// Whether this is a function with a variadict parameter pub variadic: bool, - /// Function attributes used to provide extra information about function's components - pub fn_attrs: Option, } -/// Extra information about a function's components +/// Information about a function's return #[cfg_attr(feature = "extra-traits", derive(Debug))] -#[derive(Clone, Default)] -pub struct FunctionAttributes { - /// Function's return attributes - pub ret: FunctionComponentAttributes, - /// Function's arguments attributes - pub args: Vec, +#[derive(Clone)] +pub struct FunctionReturnData { + /// Specifies the type of the function's return + pub r#type: Option, + /// Specifies the return type override provided by attributes + pub ty_override: Option, + /// Specifies the return description + pub desc: Option, } -/// Information about a function's component +/// Information about a function's argument #[cfg_attr(feature = "extra-traits", derive(Debug))] -#[derive(Clone, Default)] -pub struct FunctionComponentAttributes { - /// Specifies the type for a function component - pub ty: Option, - /// Description of the function component +#[derive(Clone)] +pub struct FunctionArgumentData { + /// Specifies the argument name + pub js_name: Option, + /// Specifies the type of the function's argument + pub pat_type: syn::PatType, + /// Specifies the argument type override provided by attributes + pub ty_override: Option, + /// Specifies the argument description pub desc: Option, - /// Specifies a name of the function argument - pub name: Option, } /// Information about a Struct being exported diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index c8decb4a136..7b911f9242b 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -637,7 +637,7 @@ impl TryToTokens for ast::Export { let mut argtys = Vec::new(); for (i, arg) in self.function.arguments.iter().enumerate() { - argtys.push(&*arg.ty); + argtys.push(&*arg.pat_type.ty); let i = i + offset; let ident = Ident::new(&format!("arg{}", i), Span::call_site()); fn unwrap_nested_types(ty: &syn::Type) -> &syn::Type { @@ -647,7 +647,7 @@ impl TryToTokens for ast::Export { _ => ty, } } - let ty = unwrap_nested_types(&arg.ty); + let ty = unwrap_nested_types(&arg.pat_type.ty); match &ty { syn::Type::Reference(syn::TypeReference { @@ -720,7 +720,7 @@ impl TryToTokens for ast::Export { elems: Default::default(), paren_token: Default::default(), }); - let syn_ret = self.function.ret.as_ref().unwrap_or(&syn_unit); + let syn_ret = self.function.ret.r#type.as_ref().unwrap_or(&syn_unit); if let syn::Type::Reference(_) = syn_ret { bail_span!(syn_ret, "cannot return a borrowed ref with #[wasm_bindgen]",) } @@ -1323,7 +1323,7 @@ impl TryToTokens for ast::ImportFunction { ast::ImportFunctionKind::Normal => {} } let vis = &self.function.rust_vis; - let ret = match &self.function.ret { + let ret = match &self.function.ret.r#type { Some(ty) => quote! { -> #ty }, None => quote!(), }; @@ -1337,8 +1337,8 @@ impl TryToTokens for ast::ImportFunction { let wasm_bindgen_futures = &self.wasm_bindgen_futures; for (i, arg) in self.function.arguments.iter().enumerate() { - let ty = &arg.ty; - let name = match &*arg.pat { + let ty = &arg.pat_type.ty; + let name = match &*arg.pat_type.pat { syn::Pat::Ident(syn::PatIdent { by_ref: None, ident, @@ -1347,7 +1347,7 @@ impl TryToTokens for ast::ImportFunction { }) => ident.clone(), syn::Pat::Wild(_) => syn::Ident::new(&format!("__genarg_{}", i), Span::call_site()), _ => bail_span!( - arg.pat, + arg.pat_type.pat, "unsupported pattern in #[wasm_bindgen] imported function", ), }; @@ -1542,7 +1542,7 @@ impl ToTokens for DescribeImport<'_> { ast::ImportKind::Type(_) => return, ast::ImportKind::Enum(_) => return, }; - let argtys = f.function.arguments.iter().map(|arg| &arg.ty); + let argtys = f.function.arguments.iter().map(|arg| &arg.pat_type.ty); let nargs = f.function.arguments.len() as u32; let inform_ret = match &f.js_ret { Some(ref t) => quote! { <#t as WasmDescribe>::describe(); }, diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index 765898c8567..b34ef3050f6 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -221,29 +221,25 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi .enumerate() .map(|(idx, arg)| { // use argument's "js_name" if it was provided in attributes - if let Some(arg_js_name) = func - .fn_attrs - .as_ref() - .and_then(|attrs| attrs.args.get(idx).and_then(|v| v.name.clone())) - { - return arg_js_name; + if let Some(name) = &arg.js_name { + return name.clone(); } - if let syn::Pat::Ident(x) = &*arg.pat { + if let syn::Pat::Ident(x) = &*arg.pat_type.pat { return x.ident.unraw().to_string(); } format!("arg{}", idx) }) .collect::>(); - let fn_attrs = func.fn_attrs.as_ref().map(|attrs| FunctionAttributes { + let fn_attrs = Some(FunctionAttributes { ret: FunctionComponentAttributes { - ty: attrs.ret.ty.clone(), - desc: attrs.ret.desc.clone(), + ty: func.ret.ty_override.clone(), + desc: func.ret.desc.clone(), }, - args: attrs - .args + args: func + .arguments .iter() .map(|arg_attr| FunctionComponentAttributes { - ty: arg_attr.ty.clone(), + ty: arg_attr.ty_override.clone(), desc: arg_attr.desc.clone(), }) .collect::>(), diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index b6df791061f..2d7356b5105 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use std::str::Chars; use ast::OperationKind; -use backend::ast::{self, FunctionAttributes, FunctionComponentAttributes, ThreadLocal}; +use backend::ast::{self, FunctionArgumentData, ThreadLocal}; use backend::util::{ident_ty, ShortHash}; use backend::Diagnostic; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; @@ -635,9 +635,9 @@ impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option &**elem, _ => bail_span!( - class.ty, + class.pat_type.ty, "first argument of method must be a shared reference" ), }; @@ -961,10 +961,13 @@ impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option for syn::ItemFn { +impl ConvertToAst<(BindgenAttrs, Vec)> for syn::ItemFn { type Target = ast::Function; - fn convert(self, attrs: BindgenAttrs) -> Result { + fn convert( + self, + (attrs, args_attrs): (BindgenAttrs, Vec), + ) -> Result { match self.vis { syn::Visibility::Public(_) => {} _ if attrs.start().is_some() => {} @@ -984,7 +987,7 @@ impl ConvertToAst for syn::ItemFn { self.attrs, self.vis, FunctionPosition::Free, - None, + Some(args_attrs), )?; attrs.check_used(); @@ -1033,7 +1036,7 @@ fn function_from_decl( attrs: Vec, vis: syn::Visibility, position: FunctionPosition, - fn_attrs: Option, + args_attrs: Option>, ) -> Result<(ast::Function, Option), Diagnostic> { if sig.variadic.is_some() { bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions"); @@ -1146,13 +1149,15 @@ fn function_from_decl( (decl_name.unraw().to_string(), decl_name.span(), false) }; + let ret_ty_override = opts.unchecked_return_type().map(|v| v.0.to_string()); + let ret_desc = opts.return_description().map(|v| v.0.to_string()); + + let args_len = arguments.len(); Ok(( ast::Function { - arguments, name_span, name, renamed_via_js_name, - ret, rust_attrs: attrs, rust_vis: vis, r#unsafe: sig.unsafety.is_some(), @@ -1160,22 +1165,40 @@ fn function_from_decl( generate_typescript: opts.skip_typescript().is_none(), generate_jsdoc: opts.skip_jsdoc().is_none(), variadic: opts.variadic().is_some(), - fn_attrs, + ret: ast::FunctionReturnData { + r#type: ret, + ty_override: ret_ty_override, + desc: ret_desc, + }, + arguments: arguments + .into_iter() + .zip(args_attrs.unwrap_or(vec![FunctionArgAttributes::default(); args_len])) + .map(|(arg_pat_type, arg_attrs)| FunctionArgumentData { + pat_type: arg_pat_type, + js_name: arg_attrs.name, + desc: arg_attrs.desc, + ty_override: arg_attrs.ty, + }) + .collect::>(), }, method_self, )) } -/// Extracts function attributes -fn extract_fn_attrs( - sig: &mut syn::Signature, - attrs: &BindgenAttrs, -) -> Result { +#[derive(Default, Clone)] +struct FunctionArgAttributes { + ty: Option, + desc: Option, + name: Option, +} + +/// Extracts function arguments attributes +fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagnostic> { let mut args_attrs = vec![]; for input in sig.inputs.iter_mut() { if let syn::FnArg::Typed(pat_type) = input { let attrs = BindgenAttrs::find(&mut pat_type.attrs)?; - let arg_attrs = FunctionComponentAttributes { + let arg_attrs = FunctionArgAttributes { ty: attrs.unchecked_param_type().map(|v| v.0.to_string()), desc: attrs.param_description().map(|v| v.0.to_string()), name: attrs.js_name().map(|v| v.0.to_string()), @@ -1184,14 +1207,7 @@ fn extract_fn_attrs( args_attrs.push(arg_attrs); } } - Ok(FunctionAttributes { - args: args_attrs, - ret: FunctionComponentAttributes { - ty: attrs.unchecked_return_type().map(|v| v.0.to_string()), - desc: attrs.return_description().map(|v| v.0.to_string()), - name: None, - }, - }) + Ok(args_attrs) } pub(crate) trait MacroParse { @@ -1234,8 +1250,8 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { if let Some((i, _)) = no_mangle { f.attrs.remove(i); } - // extract fn components attributes before parsing to tokens stream - let fn_attrs = extract_fn_attrs(&mut f.sig, &opts)?; + // extract fn args attributes before parsing to tokens stream + let args_attrs = extract_args_attrs(&mut f.sig)?; let comments = extract_doc_comments(&f.attrs); // If the function isn't used for anything other than being exported to JS, // it'll be unused when not building for the Wasm target and produce a @@ -1256,13 +1272,10 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { }); let rust_name = f.sig.ident.clone(); let start = opts.start().is_some(); - let mut function = f.convert(opts)?; - // set fn components attributes that was extracted previously - function.fn_attrs = Some(fn_attrs); program.exports.push(ast::Export { comments, - function, + function: f.convert((opts, args_attrs))?, js_class: None, method_kind, method_self: None, @@ -1451,7 +1464,7 @@ impl MacroParse<&ClassMarker> for &mut syn::ImplItemFn { let opts = BindgenAttrs::find(&mut self.attrs)?; let comments = extract_doc_comments(&self.attrs); - let fn_attrs = extract_fn_attrs(&mut self.sig, &opts)?; + let args_attrs: Vec = extract_args_attrs(&mut self.sig)?; let (function, method_self) = function_from_decl( &self.sig.ident, &opts, @@ -1459,7 +1472,7 @@ impl MacroParse<&ClassMarker> for &mut syn::ImplItemFn { self.attrs.clone(), self.vis.clone(), FunctionPosition::Impl { self_ty: class }, - Some(fn_attrs), + Some(args_attrs), )?; let method_kind = if opts.constructor().is_some() { ast::MethodKind::Constructor diff --git a/crates/macro/ui-tests/unused-attributes.rs b/crates/macro/ui-tests/unused-attributes.rs index 715828f03b9..46ddd0e4cc6 100644 --- a/crates/macro/ui-tests/unused-attributes.rs +++ b/crates/macro/ui-tests/unused-attributes.rs @@ -29,21 +29,21 @@ impl MyStruct { } } -#[wasm_bindgen(return_type = "something", return_description = "something")] +#[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] struct B {} -#[wasm_bindgen(return_type = "something", return_description = "something")] +#[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] impl B { #[wasm_bindgen] pub fn foo() {} } -#[wasm_bindgen(return_type = "something", return_description = "something")] +#[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] pub enum D { Variat } -#[wasm_bindgen(return_type = "something", return_description = "something")] +#[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] impl D { #[wasm_bindgen] pub fn foo() {} diff --git a/crates/macro/ui-tests/unused-attributes.stderr b/crates/macro/ui-tests/unused-attributes.stderr index 2fa17e488c9..4aa06791454 100644 --- a/crates/macro/ui-tests/unused-attributes.stderr +++ b/crates/macro/ui-tests/unused-attributes.stderr @@ -40,50 +40,50 @@ error: unused variable: `final` 24 | #[wasm_bindgen(getter_with_clone, final)] | ^^^^^ help: if this is intentional, prefix it with an underscore: `_final` -error: unused variable: `return_type` +error: unused variable: `unchecked_return_type` --> ui-tests/unused-attributes.rs:32:16 | -32 | #[wasm_bindgen(return_type = "something", return_description = "something")] - | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_type` +32 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] + | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` error: unused variable: `return_description` - --> ui-tests/unused-attributes.rs:32:43 + --> ui-tests/unused-attributes.rs:32:53 | -32 | #[wasm_bindgen(return_type = "something", return_description = "something")] - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` +32 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` -error: unused variable: `return_type` +error: unused variable: `unchecked_return_type` --> ui-tests/unused-attributes.rs:35:16 | -35 | #[wasm_bindgen(return_type = "something", return_description = "something")] - | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_type` +35 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] + | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` error: unused variable: `return_description` - --> ui-tests/unused-attributes.rs:35:43 + --> ui-tests/unused-attributes.rs:35:53 | -35 | #[wasm_bindgen(return_type = "something", return_description = "something")] - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` +35 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` -error: unused variable: `return_type` +error: unused variable: `unchecked_return_type` --> ui-tests/unused-attributes.rs:41:16 | -41 | #[wasm_bindgen(return_type = "something", return_description = "something")] - | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_type` +41 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] + | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` error: unused variable: `return_description` - --> ui-tests/unused-attributes.rs:41:43 + --> ui-tests/unused-attributes.rs:41:53 | -41 | #[wasm_bindgen(return_type = "something", return_description = "something")] - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` +41 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` -error: unused variable: `return_type` +error: unused variable: `unchecked_return_type` --> ui-tests/unused-attributes.rs:46:16 | -46 | #[wasm_bindgen(return_type = "something", return_description = "something")] - | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_type` +46 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] + | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` error: unused variable: `return_description` - --> ui-tests/unused-attributes.rs:46:43 + --> ui-tests/unused-attributes.rs:46:53 | -46 | #[wasm_bindgen(return_type = "something", return_description = "something")] - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` +46 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` From c25c5dde13b0da1c2ad957b5193fac6c4d32a872 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 8 Jan 2025 01:34:23 +0000 Subject: [PATCH 18/50] Update parser.rs --- crates/macro-support/src/parser.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 2d7356b5105..bfa4999dc8b 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -961,12 +961,12 @@ impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option)> for syn::ItemFn { +impl ConvertToAst<(BindgenAttrs, Vec)> for syn::ItemFn { type Target = ast::Function; fn convert( self, - (attrs, args_attrs): (BindgenAttrs, Vec), + (attrs, args_attrs): (BindgenAttrs, Vec), ) -> Result { match self.vis { syn::Visibility::Public(_) => {} @@ -1036,7 +1036,7 @@ fn function_from_decl( attrs: Vec, vis: syn::Visibility, position: FunctionPosition, - args_attrs: Option>, + args_attrs: Option>, ) -> Result<(ast::Function, Option), Diagnostic> { if sig.variadic.is_some() { bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions"); @@ -1172,7 +1172,7 @@ fn function_from_decl( }, arguments: arguments .into_iter() - .zip(args_attrs.unwrap_or(vec![FunctionArgAttributes::default(); args_len])) + .zip(args_attrs.unwrap_or(vec![FnArgAttrs::default(); args_len])) .map(|(arg_pat_type, arg_attrs)| FunctionArgumentData { pat_type: arg_pat_type, js_name: arg_attrs.name, @@ -1186,19 +1186,19 @@ fn function_from_decl( } #[derive(Default, Clone)] -struct FunctionArgAttributes { +struct FnArgAttrs { ty: Option, desc: Option, name: Option, } /// Extracts function arguments attributes -fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagnostic> { +fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagnostic> { let mut args_attrs = vec![]; for input in sig.inputs.iter_mut() { if let syn::FnArg::Typed(pat_type) = input { let attrs = BindgenAttrs::find(&mut pat_type.attrs)?; - let arg_attrs = FunctionArgAttributes { + let arg_attrs = FnArgAttrs { ty: attrs.unchecked_param_type().map(|v| v.0.to_string()), desc: attrs.param_description().map(|v| v.0.to_string()), name: attrs.js_name().map(|v| v.0.to_string()), @@ -1464,7 +1464,7 @@ impl MacroParse<&ClassMarker> for &mut syn::ImplItemFn { let opts = BindgenAttrs::find(&mut self.attrs)?; let comments = extract_doc_comments(&self.attrs); - let args_attrs: Vec = extract_args_attrs(&mut self.sig)?; + let args_attrs: Vec = extract_args_attrs(&mut self.sig)?; let (function, method_self) = function_from_decl( &self.sig.ident, &opts, From e46dedaa51522be96f42feefdd8ae3e68c441855 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 8 Jan 2025 01:38:29 +0000 Subject: [PATCH 19/50] update test --- crates/macro/ui-tests/unused-attributes.rs | 8 +-- .../macro/ui-tests/unused-attributes.stderr | 72 +++++++++++++++---- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/crates/macro/ui-tests/unused-attributes.rs b/crates/macro/ui-tests/unused-attributes.rs index 46ddd0e4cc6..019e67abb37 100644 --- a/crates/macro/ui-tests/unused-attributes.rs +++ b/crates/macro/ui-tests/unused-attributes.rs @@ -29,21 +29,21 @@ impl MyStruct { } } -#[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] +#[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] struct B {} -#[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] +#[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] impl B { #[wasm_bindgen] pub fn foo() {} } -#[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] +#[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] pub enum D { Variat } -#[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] +#[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] impl D { #[wasm_bindgen] pub fn foo() {} diff --git a/crates/macro/ui-tests/unused-attributes.stderr b/crates/macro/ui-tests/unused-attributes.stderr index 4aa06791454..f160e3eb6b7 100644 --- a/crates/macro/ui-tests/unused-attributes.stderr +++ b/crates/macro/ui-tests/unused-attributes.stderr @@ -43,47 +43,95 @@ error: unused variable: `final` error: unused variable: `unchecked_return_type` --> ui-tests/unused-attributes.rs:32:16 | -32 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] +32 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_descripti... | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` error: unused variable: `return_description` --> ui-tests/unused-attributes.rs:32:53 | -32 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` +32 | ...e = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` + +error: unused variable: `unchecked_param_type` + --> ui-tests/unused-attributes.rs:32:87 + | +32 | ...= "something", unchecked_param_type = "something", param_description = "somthing")] + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` + +error: unused variable: `param_description` + --> ui-tests/unused-attributes.rs:32:123 + | +32 | ...pe = "something", param_description = "somthing")] + | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` error: unused variable: `unchecked_return_type` --> ui-tests/unused-attributes.rs:35:16 | -35 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] +35 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_descripti... | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` error: unused variable: `return_description` --> ui-tests/unused-attributes.rs:35:53 | -35 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` +35 | ...e = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` + +error: unused variable: `unchecked_param_type` + --> ui-tests/unused-attributes.rs:35:87 + | +35 | ...= "something", unchecked_param_type = "something", param_description = "somthing")] + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` + +error: unused variable: `param_description` + --> ui-tests/unused-attributes.rs:35:123 + | +35 | ...pe = "something", param_description = "somthing")] + | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` error: unused variable: `unchecked_return_type` --> ui-tests/unused-attributes.rs:41:16 | -41 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] +41 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_descripti... | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` error: unused variable: `return_description` --> ui-tests/unused-attributes.rs:41:53 | -41 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` +41 | ...e = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` + +error: unused variable: `unchecked_param_type` + --> ui-tests/unused-attributes.rs:41:87 + | +41 | ...= "something", unchecked_param_type = "something", param_description = "somthing")] + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` + +error: unused variable: `param_description` + --> ui-tests/unused-attributes.rs:41:123 + | +41 | ...pe = "something", param_description = "somthing")] + | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` error: unused variable: `unchecked_return_type` --> ui-tests/unused-attributes.rs:46:16 | -46 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] +46 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_descripti... | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` error: unused variable: `return_description` --> ui-tests/unused-attributes.rs:46:53 | -46 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something")] - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` +46 | ...e = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` + +error: unused variable: `unchecked_param_type` + --> ui-tests/unused-attributes.rs:46:87 + | +46 | ...= "something", unchecked_param_type = "something", param_description = "somthing")] + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` + +error: unused variable: `param_description` + --> ui-tests/unused-attributes.rs:46:123 + | +46 | ...pe = "something", param_description = "somthing")] + | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` From 9507ed8e7dd095479c2cbb2618e5be84b03d33b0 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 8 Jan 2025 01:40:12 +0000 Subject: [PATCH 20/50] update test --- crates/macro/ui-tests/unused-attributes.rs | 28 +++++- .../macro/ui-tests/unused-attributes.stderr | 96 +++++++++---------- 2 files changed, 72 insertions(+), 52 deletions(-) diff --git a/crates/macro/ui-tests/unused-attributes.rs b/crates/macro/ui-tests/unused-attributes.rs index 019e67abb37..030254d8087 100644 --- a/crates/macro/ui-tests/unused-attributes.rs +++ b/crates/macro/ui-tests/unused-attributes.rs @@ -29,21 +29,41 @@ impl MyStruct { } } -#[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] +#[wasm_bindgen( + unchecked_return_type = "something", + return_description = "something", + unchecked_param_type = "something", + param_description = "somthing" +)] struct B {} -#[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] +#[wasm_bindgen( + unchecked_return_type = "something", + return_description = "something", + unchecked_param_type = "something", + param_description = "somthing" +)] impl B { #[wasm_bindgen] pub fn foo() {} } -#[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] +#[wasm_bindgen( + unchecked_return_type = "something", + return_description = "something", + unchecked_param_type = "something", + param_description = "somthing" +)] pub enum D { Variat } -#[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] +#[wasm_bindgen( + unchecked_return_type = "something", + return_description = "something", + unchecked_param_type = "something", + param_description = "somthing" +)] impl D { #[wasm_bindgen] pub fn foo() {} diff --git a/crates/macro/ui-tests/unused-attributes.stderr b/crates/macro/ui-tests/unused-attributes.stderr index f160e3eb6b7..3e1786c3da5 100644 --- a/crates/macro/ui-tests/unused-attributes.stderr +++ b/crates/macro/ui-tests/unused-attributes.stderr @@ -41,97 +41,97 @@ error: unused variable: `final` | ^^^^^ help: if this is intentional, prefix it with an underscore: `_final` error: unused variable: `unchecked_return_type` - --> ui-tests/unused-attributes.rs:32:16 + --> ui-tests/unused-attributes.rs:33:5 | -32 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_descripti... - | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` +33 | unchecked_return_type = "something", + | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` error: unused variable: `return_description` - --> ui-tests/unused-attributes.rs:32:53 + --> ui-tests/unused-attributes.rs:34:5 | -32 | ...e = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` +34 | return_description = "something", + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` error: unused variable: `unchecked_param_type` - --> ui-tests/unused-attributes.rs:32:87 + --> ui-tests/unused-attributes.rs:35:5 | -32 | ...= "something", unchecked_param_type = "something", param_description = "somthing")] - | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` +35 | unchecked_param_type = "something", + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` error: unused variable: `param_description` - --> ui-tests/unused-attributes.rs:32:123 + --> ui-tests/unused-attributes.rs:36:5 | -32 | ...pe = "something", param_description = "somthing")] - | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` +36 | param_description = "somthing" + | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` error: unused variable: `unchecked_return_type` - --> ui-tests/unused-attributes.rs:35:16 + --> ui-tests/unused-attributes.rs:41:5 | -35 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_descripti... - | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` +41 | unchecked_return_type = "something", + | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` error: unused variable: `return_description` - --> ui-tests/unused-attributes.rs:35:53 + --> ui-tests/unused-attributes.rs:42:5 | -35 | ...e = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` +42 | return_description = "something", + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` error: unused variable: `unchecked_param_type` - --> ui-tests/unused-attributes.rs:35:87 + --> ui-tests/unused-attributes.rs:43:5 | -35 | ...= "something", unchecked_param_type = "something", param_description = "somthing")] - | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` +43 | unchecked_param_type = "something", + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` error: unused variable: `param_description` - --> ui-tests/unused-attributes.rs:35:123 + --> ui-tests/unused-attributes.rs:44:5 | -35 | ...pe = "something", param_description = "somthing")] - | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` +44 | param_description = "somthing" + | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` error: unused variable: `unchecked_return_type` - --> ui-tests/unused-attributes.rs:41:16 + --> ui-tests/unused-attributes.rs:52:5 | -41 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_descripti... - | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` +52 | unchecked_return_type = "something", + | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` error: unused variable: `return_description` - --> ui-tests/unused-attributes.rs:41:53 + --> ui-tests/unused-attributes.rs:53:5 | -41 | ...e = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` +53 | return_description = "something", + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` error: unused variable: `unchecked_param_type` - --> ui-tests/unused-attributes.rs:41:87 + --> ui-tests/unused-attributes.rs:54:5 | -41 | ...= "something", unchecked_param_type = "something", param_description = "somthing")] - | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` +54 | unchecked_param_type = "something", + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` error: unused variable: `param_description` - --> ui-tests/unused-attributes.rs:41:123 + --> ui-tests/unused-attributes.rs:55:5 | -41 | ...pe = "something", param_description = "somthing")] - | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` +55 | param_description = "somthing" + | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` error: unused variable: `unchecked_return_type` - --> ui-tests/unused-attributes.rs:46:16 + --> ui-tests/unused-attributes.rs:62:5 | -46 | #[wasm_bindgen(unchecked_return_type = "something", return_description = "something", unchecked_param_type = "something", param_descripti... - | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` +62 | unchecked_return_type = "something", + | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` error: unused variable: `return_description` - --> ui-tests/unused-attributes.rs:46:53 + --> ui-tests/unused-attributes.rs:63:5 | -46 | ...e = "something", return_description = "something", unchecked_param_type = "something", param_description = "somthing")] - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` +63 | return_description = "something", + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` error: unused variable: `unchecked_param_type` - --> ui-tests/unused-attributes.rs:46:87 + --> ui-tests/unused-attributes.rs:64:5 | -46 | ...= "something", unchecked_param_type = "something", param_description = "somthing")] - | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` +64 | unchecked_param_type = "something", + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` error: unused variable: `param_description` - --> ui-tests/unused-attributes.rs:46:123 + --> ui-tests/unused-attributes.rs:65:5 | -46 | ...pe = "something", param_description = "somthing")] - | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` +65 | param_description = "somthing" + | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` From d8610762dabf4ba3513bf4ca66a87e8be582a847 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 8 Jan 2025 01:44:21 +0000 Subject: [PATCH 21/50] Update parser.rs --- crates/macro-support/src/parser.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index bfa4999dc8b..fe878835490 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -961,12 +961,12 @@ impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option)> for syn::ItemFn { +impl ConvertToAst<(BindgenAttrs, Option>)> for syn::ItemFn { type Target = ast::Function; fn convert( self, - (attrs, args_attrs): (BindgenAttrs, Vec), + (attrs, args_attrs): (BindgenAttrs, Option>), ) -> Result { match self.vis { syn::Visibility::Public(_) => {} @@ -987,7 +987,7 @@ impl ConvertToAst<(BindgenAttrs, Vec)> for syn::ItemFn { self.attrs, self.vis, FunctionPosition::Free, - Some(args_attrs), + args_attrs, )?; attrs.check_used(); @@ -1275,7 +1275,7 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { program.exports.push(ast::Export { comments, - function: f.convert((opts, args_attrs))?, + function: f.convert((opts, Some(args_attrs)))?, js_class: None, method_kind, method_self: None, From 935286e67bd056cc71d69fdb4dbfc89b938733f8 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 8 Jan 2025 02:10:59 +0000 Subject: [PATCH 22/50] Update function-attributes.md --- .../reference/attributes/on-rust-exports/function-attributes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/reference/attributes/on-rust-exports/function-attributes.md b/guide/src/reference/attributes/on-rust-exports/function-attributes.md index 6e07cd2c9f9..74ebdc494b4 100644 --- a/guide/src/reference/attributes/on-rust-exports/function-attributes.md +++ b/guide/src/reference/attributes/on-rust-exports/function-attributes.md @@ -42,7 +42,7 @@ export function build_person(personName: string): Promise; As you can see, using function attributes, we can now return a JS/TS object without ever using `wasm_bindgen` macro on `Person` struct that would have generated a JS/TS class object in the bindings which might not be the desired outcome for every case, you can see as well that instead of handcoding the full documentation for `build_person()` with all JSDoc/TSDoc tags and syntax, we can just write specific description for each individual component of the function and as a result end up with a fully typed and documented function in the bindings. **IMPORTANT NOTE** -Types that are provided using `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(unchecked_param_type)]` aren't checked as these attributes' identifiers entail, meaning they will end up in the function's signature and docs bindings exactly as they have been specified and there are no checks/validation for them in place, so only because a user uses '#[wasm_bindgen(unchecked_param_type = "number")]' for example, it doesn't necessarily mean it's actually going to be a value of number type, therefore validation and checks between the value and its type should be handled by the user and the responsibility of using them correctly and carefully relies solely on the user. +Types that are provided using `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(unchecked_param_type)]` aren't checked as these attributes' identifiers entail, meaning they will end up in the function's signature and docs bindings exactly as they have been specified and there are no checks/validation for them in place, so only because a user uses `#[wasm_bindgen(unchecked_param_type = "number")]` for example, it doesn't necessarily mean it's actually going to be a value of number type, therefore validation and checks between the value and its type should be handled by the user and the responsibility of using them correctly and carefully relies solely on the user. Let's look at some more exmaples in details: ```rust From 19bfb47192086f246aff87f19a104cf10f451fc0 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 8 Jan 2025 02:25:01 +0000 Subject: [PATCH 23/50] Update ast.rs --- crates/backend/src/ast.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 21edf513c8a..0ece7008674 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -376,7 +376,7 @@ pub struct Function { pub renamed_via_js_name: bool, /// The arguments to the function pub arguments: Vec, - /// The return type of the function, if provided + /// The data of return type of the function pub ret: FunctionReturnData, /// Any custom attributes being applied to the function pub rust_attrs: Vec, From 5707447b543807739a57fe32d6cf43b3a5b1ed11 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Wed, 8 Jan 2025 18:17:27 +0000 Subject: [PATCH 24/50] fix docs --- .../on-rust-exports/function-attributes.md | 43 ++----------------- 1 file changed, 3 insertions(+), 40 deletions(-) diff --git a/guide/src/reference/attributes/on-rust-exports/function-attributes.md b/guide/src/reference/attributes/on-rust-exports/function-attributes.md index 74ebdc494b4..021de749d10 100644 --- a/guide/src/reference/attributes/on-rust-exports/function-attributes.md +++ b/guide/src/reference/attributes/on-rust-exports/function-attributes.md @@ -1,48 +1,11 @@ # `function-attributes` -By default, exported Rust functions and methods generate function signature from equivalent Rust types identifiers without any arguments and return variable documentations unless completely handcoded using `skip-jsdoc` and `typescript-custom-section` with `skip_typescript`, however by using function attributes, it's possible to override a function's return type and arguments names and types for generated bindings as well as the ability to write specific documentation for each of them individually as desired: +By default, exported Rust functions and methods generate function signature from equivalent Rust types identifiers without any arguments and return variable documentations. However by using function attributes, it's possible to override a function's return type and arguments names and types for generated bindings as well as the ability to write specific documentation for each of them individually as desired: - `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(return_description)]` used to override a function's return type and to specify descriptions for generated JS/TS bindings. - `#[wasm_bindgen(js_name)]`, `#[wasm_bindgen(unchecked_param_type)]` and `#[wasm_bindgen(param_description)]` applied to a Rust function argument to override that argument's name and type and to specify descriptions for generated JS/TS bindings. -For example a Rust function can return `JsValue` by serializing a Rust type using serde, yet on generated TS bindings instead of `any` as the return type, it can be overridden to the TS interface of the equivalent of the serialized Rust type defined using `typescript-custom-section`: -```rust -// we wont use "#[wasm_bindgen]" for this struct -#[derive(serde::Serialize, serde::Deserialize)] -pub struct Person { - pub name: String; -} - -// define a TS interface equivalent of Person struct -#[wasm_bindgen(typescript_custom_section)] -const TS_INTERFACE_EXPORT: &'static str = r" -export interface Person { name: string; } -"; - -#[wasm_bindgen(unchecked_return_type = "Person", return_description = "a Person object")] -pub async fn build_person( - #[wasm_bindgen(js_name = "personName", param_description = "Specifies the person's name")] - person_name: String, -) -> Result { - // - // some async operations - // - Ok(serde_wasm_bindgen::to_value(&Person { name: person_name })?) -} -``` -this will generate the following TS bindings: -```ts -export interface Person { name: string; } - -/** -* @param personName - Specifies the person's name -* @returns a Person object -*/ -export function build_person(personName: string): Promise; -``` -As you can see, using function attributes, we can now return a JS/TS object without ever using `wasm_bindgen` macro on `Person` struct that would have generated a JS/TS class object in the bindings which might not be the desired outcome for every case, you can see as well that instead of handcoding the full documentation for `build_person()` with all JSDoc/TSDoc tags and syntax, we can just write specific description for each individual component of the function and as a result end up with a fully typed and documented function in the bindings. - -**IMPORTANT NOTE** -Types that are provided using `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(unchecked_param_type)]` aren't checked as these attributes' identifiers entail, meaning they will end up in the function's signature and docs bindings exactly as they have been specified and there are no checks/validation for them in place, so only because a user uses `#[wasm_bindgen(unchecked_param_type = "number")]` for example, it doesn't necessarily mean it's actually going to be a value of number type, therefore validation and checks between the value and its type should be handled by the user and the responsibility of using them correctly and carefully relies solely on the user. +> **NOTE**: +> Types that are provided using `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(unchecked_param_type)]` aren't checked as these attributes' identifiers entail, meaning they will end up in the function's signature and docs bindings exactly as they have been specified and there are no checks/validation for them in place, so only because a user uses `#[wasm_bindgen(unchecked_param_type = "number")]` for example, it doesn't necessarily mean it's actually going to be a value of number type, therefore validation and checks between the value and its type should be handled by the user and the responsibility of using them correctly and carefully relies solely on the user. Let's look at some more exmaples in details: ```rust From 375570de9662fa66d3eb5c80a928876d090a1d27 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 9 Jan 2025 00:08:01 +0000 Subject: [PATCH 25/50] impl requested changes --- crates/backend/src/ast.rs | 4 +- crates/backend/src/codegen.rs | 9 +- crates/backend/src/encode.rs | 44 +++---- crates/cli-support/src/decode.rs | 19 +-- crates/cli-support/src/js/binding.rs | 115 +++++++++--------- crates/cli-support/src/js/mod.rs | 24 ++-- crates/cli-support/src/wit/mod.rs | 15 ++- crates/cli-support/src/wit/nonstandard.rs | 10 +- crates/macro-support/src/parser.rs | 99 ++++++++++++--- crates/macro/ui-tests/dup-fn-attrs.rs | 31 +++++ crates/macro/ui-tests/dup-fn-attrs.stderr | 17 +++ .../macro/ui-tests/illegal-char-fn-attrs.rs | 34 ++++++ .../ui-tests/illegal-char-fn-attrs.stderr | 29 +++++ crates/macro/ui-tests/no-ret-fn-attr.rs | 21 ++++ crates/macro/ui-tests/no-ret-fn-attr.stderr | 23 ++++ crates/shared/src/lib.rs | 15 +-- crates/shared/src/schema_hash_approval.rs | 2 +- 17 files changed, 369 insertions(+), 142 deletions(-) create mode 100644 crates/macro/ui-tests/dup-fn-attrs.rs create mode 100644 crates/macro/ui-tests/dup-fn-attrs.stderr create mode 100644 crates/macro/ui-tests/illegal-char-fn-attrs.rs create mode 100644 crates/macro/ui-tests/illegal-char-fn-attrs.stderr create mode 100644 crates/macro/ui-tests/no-ret-fn-attr.rs create mode 100644 crates/macro/ui-tests/no-ret-fn-attr.stderr diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 0ece7008674..c0e8664d07e 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -377,7 +377,7 @@ pub struct Function { /// The arguments to the function pub arguments: Vec, /// The data of return type of the function - pub ret: FunctionReturnData, + pub ret: Option, /// Any custom attributes being applied to the function pub rust_attrs: Vec, /// The visibility of this function in Rust @@ -399,7 +399,7 @@ pub struct Function { #[derive(Clone)] pub struct FunctionReturnData { /// Specifies the type of the function's return - pub r#type: Option, + pub r#type: syn::Type, /// Specifies the return type override provided by attributes pub ty_override: Option, /// Specifies the return description diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 7b911f9242b..01450ccb9df 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -720,7 +720,12 @@ impl TryToTokens for ast::Export { elems: Default::default(), paren_token: Default::default(), }); - let syn_ret = self.function.ret.r#type.as_ref().unwrap_or(&syn_unit); + let syn_ret = self + .function + .ret + .as_ref() + .map(|ret| &ret.r#type) + .unwrap_or(&syn_unit); if let syn::Type::Reference(_) = syn_ret { bail_span!(syn_ret, "cannot return a borrowed ref with #[wasm_bindgen]",) } @@ -1323,7 +1328,7 @@ impl TryToTokens for ast::ImportFunction { ast::ImportFunctionKind::Normal => {} } let vis = &self.function.rust_vis; - let ret = match &self.function.ret.r#type { + let ret = match self.function.ret.as_ref().map(|ret| &ret.r#type) { Some(ty) => quote! { -> #ty }, None => quote!(), }; diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index b34ef3050f6..39de7963a00 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -215,43 +215,37 @@ fn shared_export<'a>( } fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Function<'a> { - let arg_names = func + let args = func .arguments .iter() .enumerate() - .map(|(idx, arg)| { + .map(|(idx, arg)| FunctionArgumentData { // use argument's "js_name" if it was provided in attributes - if let Some(name) = &arg.js_name { - return name.clone(); - } - if let syn::Pat::Ident(x) = &*arg.pat_type.pat { - return x.ident.unraw().to_string(); - } - format!("arg{}", idx) + name: if let Some(name) = &arg.js_name { + name.clone() + } else if let syn::Pat::Ident(x) = &*arg.pat_type.pat { + x.ident.unraw().to_string() + } else { + format!("arg{}", idx) + }, + ty_override: arg.ty_override.clone(), + desc: arg.desc.clone(), }) .collect::>(); - let fn_attrs = Some(FunctionAttributes { - ret: FunctionComponentAttributes { - ty: func.ret.ty_override.clone(), - desc: func.ret.desc.clone(), - }, - args: func - .arguments - .iter() - .map(|arg_attr| FunctionComponentAttributes { - ty: arg_attr.ty_override.clone(), - desc: arg_attr.desc.clone(), - }) - .collect::>(), - }); + let (ret_ty_override, ret_desc) = func + .ret + .as_ref() + .map(|ret| (ret.ty_override.clone(), ret.desc.clone())) + .unwrap_or((None, None)); Function { - arg_names, + args, asyncness: func.r#async, name: &func.name, generate_typescript: func.generate_typescript, generate_jsdoc: func.generate_jsdoc, variadic: func.variadic, - fn_attrs, + ret_ty_override, + ret_desc, } } diff --git a/crates/cli-support/src/decode.rs b/crates/cli-support/src/decode.rs index dcb3680d831..d0403b26f1f 100644 --- a/crates/cli-support/src/decode.rs +++ b/crates/cli-support/src/decode.rs @@ -94,20 +94,21 @@ impl<'src, T: Decode<'src>> Decode<'src> for Option { } } -impl Debug for FunctionAttributes { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!( - "FunctionAttributes {{ ret: {:?}, args: {:?} }}", - self.ret, self.args - )) +impl Clone for FunctionArgumentData { + fn clone(&self) -> Self { + Self { + name: self.name.clone(), + desc: self.desc.clone(), + ty_override: self.ty_override.clone(), + } } } -impl Debug for FunctionComponentAttributes { +impl Debug for FunctionArgumentData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!( - "FunctionComponentAttributes {{ ty: {:?}, desc: {:?} }}", - self.ty, self.desc + "FunctionArgumentData {{ name: {:?}, ty_override: {:?}, desc: {:?} }}", + self.name, self.ty_override, self.desc )) } } diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 112f3afa86d..88ffb7bfd56 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -4,7 +4,7 @@ //! exported functions, table elements, imports, etc. All function shims //! generated by `wasm-bindgen` run through this type. -use crate::decode::FunctionAttributes; +use crate::decode::FunctionArgumentData; use crate::js::Context; use crate::wit::InstructionData; use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction}; @@ -122,12 +122,13 @@ impl<'a, 'b> Builder<'a, 'b> { &mut self, adapter: &Adapter, instructions: &[InstructionData], - explicit_arg_names: &Option>, + args_data: &Option>, asyncness: bool, variadic: bool, generate_jsdoc: bool, debug_name: &str, - export_fn_attrs: &Option, + ret_ty_override: &Option, + ret_desc: &Option, ) -> Result { if self .cx @@ -161,11 +162,15 @@ impl<'a, 'b> Builder<'a, 'b> { } } for (i, param) in params.enumerate() { - let arg = match explicit_arg_names { + let arg = match args_data { Some(list) => list[i].clone(), - None => format!("arg{}", i), + None => FunctionArgumentData { + name: format!("arg{}", i), + ty_override: None, + desc: None, + }, }; - js.args.push(arg.clone()); + js.args.push(arg.name.clone()); function_args.push(arg); arg_tys.push(param); } @@ -225,9 +230,13 @@ impl<'a, 'b> Builder<'a, 'b> { // } let mut code = String::new(); + let args_names = function_args + .iter() + .map(|v| v.name.as_str()) + .collect::>(); code.push('('); if variadic { - if let Some((last, non_variadic_args)) = function_args.split_last() { + if let Some((last, non_variadic_args)) = args_names.split_last() { code.push_str(&non_variadic_args.join(", ")); if !non_variadic_args.is_empty() { code.push_str(", "); @@ -235,7 +244,7 @@ impl<'a, 'b> Builder<'a, 'b> { code.push_str((String::from("...") + last).as_str()) } } else { - code.push_str(&function_args.join(", ")); + code.push_str(&args_names.join(", ")); } code.push_str(") {\n"); @@ -275,7 +284,7 @@ impl<'a, 'b> Builder<'a, 'b> { &mut might_be_optional_field, asyncness, variadic, - export_fn_attrs, + ret_ty_override, ); let js_doc = if generate_jsdoc { self.js_doc_comments( @@ -283,7 +292,8 @@ impl<'a, 'b> Builder<'a, 'b> { &arg_tys, &ts_ret_ty, variadic, - export_fn_attrs, + ret_ty_override, + ret_desc, ) } else { String::new() @@ -295,7 +305,7 @@ impl<'a, 'b> Builder<'a, 'b> { // casings for arguments names such as "@param {string} [arg]" that // tags the argument as optional, for ts doc we only need arg names // and rest are just derived from function ts signature - let ts_doc = self.ts_doc_comments(&function_args, export_fn_attrs); + let ts_doc = self.ts_doc_comments(&function_args, ret_desc); Ok(JsFunction { code, @@ -318,26 +328,26 @@ impl<'a, 'b> Builder<'a, 'b> { /// return value, it doesn't include the function name in any way. fn typescript_signature( &self, - arg_names: &[String], + args_data: &[FunctionArgumentData], arg_tys: &[&AdapterType], result_tys: &[AdapterType], might_be_optional_field: &mut bool, asyncness: bool, variadic: bool, - export_fn_attrs: &Option, + ret_ty_override: &Option, ) -> (String, Vec, Option, HashSet) { - // flatten args types overrides - let args_overrides = export_fn_attrs - .as_ref() - .map(|v| v.args.iter().map(|e| e.ty.as_ref()).collect::>()) - .unwrap_or(vec![None; arg_names.len()]); - // Build up the typescript signature as well let mut omittable = true; let mut ts_args = Vec::new(); let mut ts_arg_tys = Vec::new(); let mut ts_refs = HashSet::new(); - for ((name, ty), ty_override) in arg_names.iter().zip(arg_tys).zip(args_overrides).rev() { + for ( + FunctionArgumentData { + name, ty_override, .. + }, + ty, + ) in args_data.iter().zip(arg_tys).rev() + { // In TypeScript, we can mark optional parameters as omittable // using the `?` suffix, but only if they're not followed by // non-omittable parameters. Therefore iterate the parameter list @@ -391,7 +401,6 @@ impl<'a, 'b> Builder<'a, 'b> { } // Constructors have no listed return type in typescript - let ret_ty_override = export_fn_attrs.as_ref().and_then(|v| v.ret.ty.as_ref()); let mut ts_ret = None; if self.constructor.is_none() { ts.push_str(": "); @@ -423,33 +432,29 @@ impl<'a, 'b> Builder<'a, 'b> { /// and the return value. fn js_doc_comments( &self, - arg_names: &[String], + args_data: &[FunctionArgumentData], arg_tys: &[&AdapterType], ts_ret: &Option, variadic: bool, - export_fn_attrs: &Option, + ret_ty_override: &Option, + ret_desc: &Option, ) -> String { - // flatten args attributes overrides - let args_overrides = &export_fn_attrs - .as_ref() - .map(|v| { - v.args - .iter() - .map(|e| (e.ty.as_ref(), e.desc.as_ref())) - .collect::>() - }) - .unwrap_or(vec![(None, None); arg_names.len()]); - - let (variadic_arg, fn_arg_names) = match arg_names.split_last() { + let (variadic_arg, fn_arg_names) = match args_data.split_last() { Some((last, args)) if variadic => (Some(last), args), - _ => (None, arg_names), + _ => (None, args_data), }; let mut omittable = true; let mut js_doc_args = Vec::new(); - for ((name, ty), (ty_override, desc)) in - fn_arg_names.iter().zip(arg_tys).zip(args_overrides).rev() + for ( + FunctionArgumentData { + name, + ty_override, + desc, + }, + ty, + ) in fn_arg_names.iter().zip(arg_tys).rev() { let mut arg = "@param {".to_string(); @@ -486,8 +491,14 @@ impl<'a, 'b> Builder<'a, 'b> { let mut ret: String = js_doc_args.into_iter().rev().collect(); - if let (Some(name), Some(ty), Some((ty_override, desc))) = - (variadic_arg, arg_tys.last(), args_overrides.last()) + if let ( + Some(FunctionArgumentData { + name, + ty_override, + desc, + }), + Some(ty), + ) = (variadic_arg, arg_tys.last()) { ret.push_str("@param {..."); if let Some(v) = ty_override { @@ -505,11 +516,7 @@ impl<'a, 'b> Builder<'a, 'b> { } ret.push('\n'); } - let (ret_ty_override, ret_desc) = export_fn_attrs - .as_ref() - .map(|v| (v.ret.ty.as_ref(), v.ret.desc.as_ref())) - .unwrap_or((None, None)); - if let Some(ts) = ret_ty_override.or(ts_ret.as_ref()) { + if let Some(ts) = ret_ty_override.as_ref().or(ts_ret.as_ref()) { // skip if type is void and there is no description if ts != "void" || ret_desc.is_some() { ret.push_str(&format!("@returns {{{}}}", ts)); @@ -527,18 +534,12 @@ impl<'a, 'b> Builder<'a, 'b> { /// the return value descriptions. fn ts_doc_comments( &self, - arg_names: &[String], - export_fn_attrs: &Option, + args_data: &[FunctionArgumentData], + ret_desc: &Option, ) -> String { - // flatten args desc - let args_desc = &export_fn_attrs - .as_ref() - .map(|v| v.args.iter().map(|e| e.desc.as_ref()).collect::>()) - .unwrap_or(vec![None; arg_names.len()]); - let mut ts_doc_args = Vec::new(); // ofc we dont need arg type for ts doc, only arg name - for (name, desc) in arg_names.iter().zip(args_desc) { + for FunctionArgumentData { name, desc, .. } in args_data.iter() { let mut arg = "@param ".to_string(); arg.push_str(name); @@ -555,11 +556,7 @@ impl<'a, 'b> Builder<'a, 'b> { let mut ret: String = ts_doc_args.into_iter().collect(); // only if there is return description as we dont want empty @return tag - if let Some(ret_desc) = export_fn_attrs - .as_ref() - .map(|v| v.ret.desc.as_ref()) - .unwrap_or(None) - { + if let Some(ret_desc) = ret_desc { ret.push_str("@returns "); ret.push_str(ret_desc); } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index c578cb4c780..005ea750a9a 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2829,18 +2829,20 @@ __wbg_set_wasm(wasm);" ContextAdapterKind::Import(_) => builder.cx.config.debug, }); builder.catch(catch); - let mut arg_names = &None; + let mut args = &None; let mut asyncness = false; let mut variadic = false; let mut generate_jsdoc = false; - let mut export_fn_attrs = &None; + let mut ret_ty_override = &None; + let mut ret_desc = &None; match kind { ContextAdapterKind::Export(export) => { - arg_names = &export.arg_names; + args = &export.args; asyncness = export.asyncness; variadic = export.variadic; generate_jsdoc = export.generate_jsdoc; - export_fn_attrs = &export.export_fn_attrs; + ret_ty_override = &export.fn_ret_ty_override; + ret_desc = &export.fn_ret_desc; match &export.kind { AuxExportKind::Function(_) => {} AuxExportKind::Constructor(class) => builder.constructor(class), @@ -2881,12 +2883,13 @@ __wbg_set_wasm(wasm);" .process( adapter, instrs, - arg_names, + args, asyncness, variadic, generate_jsdoc, &debug_name, - export_fn_attrs, + ret_ty_override, + ret_desc, ) .with_context(|| "failed to generates bindings for ".to_string() + &debug_name)?; @@ -2904,10 +2907,11 @@ __wbg_set_wasm(wasm);" // only include ts_doc for format if there was arguments or return var description // this is because if there are no arguments or return var description, ts_doc // provides no additional info on top of what ts_sig already does - let ts_doc_opts = export_fn_attrs.as_ref().and_then(|v| { - (v.ret.desc.is_some() || v.args.iter().any(|e| e.desc.is_some())) - .then_some(ts_doc) - }); + let ts_doc_opts = (ret_desc.is_some() + || args + .as_ref() + .is_some_and(|v| v.iter().any(|arg| arg.desc.is_some()))) + .then_some(ts_doc); let js_docs = format_doc_comments(&export.comments, Some(js_doc)); let ts_docs = format_doc_comments(&export.comments, ts_doc_opts); diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 2890d10e66e..37f1ec4c31c 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -527,13 +527,14 @@ impl<'a> Context<'a> { AuxExport { debug_name: wasm_name, comments: concatenate_comments(&export.comments), - arg_names: Some(export.function.arg_names), + args: Some(export.function.args), asyncness: export.function.asyncness, kind, generate_typescript: export.function.generate_typescript, generate_jsdoc: export.function.generate_jsdoc, variadic: export.function.variadic, - export_fn_attrs: export.function.fn_attrs, + fn_ret_ty_override: export.function.ret_ty_override, + fn_ret_desc: export.function.ret_desc, }, ); Ok(()) @@ -947,7 +948,7 @@ impl<'a> Context<'a> { getter_id, AuxExport { debug_name: format!("getter for `{}::{}`", struct_.name, field.name), - arg_names: None, + args: None, asyncness: false, comments: concatenate_comments(&field.comments), kind: AuxExportKind::Method { @@ -959,7 +960,8 @@ impl<'a> Context<'a> { generate_typescript: field.generate_typescript, generate_jsdoc: field.generate_jsdoc, variadic: false, - export_fn_attrs: None, + fn_ret_ty_override: None, + fn_ret_desc: None, }, ); @@ -980,7 +982,7 @@ impl<'a> Context<'a> { setter_id, AuxExport { debug_name: format!("setter for `{}::{}`", struct_.name, field.name), - arg_names: None, + args: None, asyncness: false, comments: concatenate_comments(&field.comments), kind: AuxExportKind::Method { @@ -992,7 +994,8 @@ impl<'a> Context<'a> { generate_typescript: field.generate_typescript, generate_jsdoc: field.generate_jsdoc, variadic: false, - export_fn_attrs: None, + fn_ret_ty_override: None, + fn_ret_desc: None, }, ); } diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index 9f06e421f2c..bfb936ddb29 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -1,4 +1,4 @@ -use crate::decode::FunctionAttributes; +use crate::decode::FunctionArgumentData; use crate::intrinsic::Intrinsic; use crate::wit::AdapterId; use std::borrow::Cow; @@ -76,7 +76,7 @@ pub struct AuxExport { pub comments: String, /// Argument names in Rust forwarded here to configure the names that show /// up in TypeScript bindings. - pub arg_names: Option>, + pub args: Option>, /// Whether this is an async function, to configure the TypeScript return value. pub asyncness: bool, /// What kind of function this is and where it shows up @@ -87,8 +87,10 @@ pub struct AuxExport { pub generate_jsdoc: bool, /// Whether typescript bindings should be generated for this export. pub variadic: bool, - /// Extra info about an exporting function components attributes - pub export_fn_attrs: Option, + /// Function's return overriding type + pub fn_ret_ty_override: Option, + /// Function's return description + pub fn_ret_desc: Option, } /// All possible kinds of exports from a Wasm module. diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index fe878835490..b87504a10c3 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -635,9 +635,9 @@ impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option None, - syn::ReturnType::Type(_, ty) => Some(replace_self(*ty)), + syn::ReturnType::Type(_, ty) => Some(ast::FunctionReturnData { + r#type: replace_self(*ty), + ty_override: ty_override.as_ref().map_or(Ok(None), |(ty, span)| { + if is_js_keyword(ty) { + return Err(Diagnostic::span_error( + *span, + "colliades with js/ts keyword", + )); + } + if contains_js_comment_syntax(ty) { + return Err(Diagnostic::span_error(*span, "contains illegal chars")); + } + Ok(Some(ty.to_string())) + })?, + desc: desc.as_ref().map_or(Ok(None), |(desc, span)| { + if contains_js_comment_syntax(desc) { + return Err(Diagnostic::span_error(*span, "contains illegal chars")); + } + Ok(Some(desc.to_string())) + })?, + }), }; + // error if specified description or type override for return + // while the function doesn't return anything + if ret.is_none() && (ty_override.is_some() || desc.is_some()) { + if let Some((_, span)) = ty_override { + return Err(Diagnostic::span_error( + span, + "cannot specify type for a function that doesn't return", + )); + } + if let Some((_, span)) = desc { + return Err(Diagnostic::span_error( + span, + "cannot specify description for a function that doesn't return", + )); + } + } let (name, name_span, renamed_via_js_name) = if let Some((js_name, js_name_span)) = opts.js_name() { @@ -1149,9 +1187,6 @@ fn function_from_decl( (decl_name.unraw().to_string(), decl_name.span(), false) }; - let ret_ty_override = opts.unchecked_return_type().map(|v| v.0.to_string()); - let ret_desc = opts.return_description().map(|v| v.0.to_string()); - let args_len = arguments.len(); Ok(( ast::Function { @@ -1165,11 +1200,7 @@ fn function_from_decl( generate_typescript: opts.skip_typescript().is_none(), generate_jsdoc: opts.skip_jsdoc().is_none(), variadic: opts.variadic().is_some(), - ret: ast::FunctionReturnData { - r#type: ret, - ty_override: ret_ty_override, - desc: ret_desc, - }, + ret, arguments: arguments .into_iter() .zip(args_attrs.unwrap_or(vec![FnArgAttrs::default(); args_len])) @@ -1199,17 +1230,55 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn if let syn::FnArg::Typed(pat_type) = input { let attrs = BindgenAttrs::find(&mut pat_type.attrs)?; let arg_attrs = FnArgAttrs { - ty: attrs.unchecked_param_type().map(|v| v.0.to_string()), - desc: attrs.param_description().map(|v| v.0.to_string()), - name: attrs.js_name().map(|v| v.0.to_string()), + name: attrs + .js_name() + .map_or(Ok(None), |(js_name_override, span)| { + if is_js_keyword(js_name_override) { + return Err(Diagnostic::span_error( + span, + "colliades with js/ts keyword", + )); + } + if contains_js_comment_syntax(js_name_override) { + return Err(Diagnostic::span_error(span, "contains illegal chars")); + } + Ok(Some(js_name_override.to_string())) + })?, + ty: attrs + .unchecked_param_type() + .map_or(Ok(None), |(ty, span)| { + if is_js_keyword(ty) { + return Err(Diagnostic::span_error( + span, + "colliades with js/ts keyword", + )); + } + if contains_js_comment_syntax(ty) { + return Err(Diagnostic::span_error(span, "contains illegal chars")); + } + Ok(Some(ty.to_string())) + })?, + desc: attrs + .param_description() + .map_or(Ok(None), |(description, span)| { + if contains_js_comment_syntax(description) { + return Err(Diagnostic::span_error(span, "contains illegal chars")); + } + Ok(Some(description.to_string())) + })?, }; - attrs.check_used(); + attrs.enforce_used()?; args_attrs.push(arg_attrs); } } Ok(args_attrs) } +/// Checks if the given string contains JS/TS comment block open/close syntax +fn contains_js_comment_syntax(str: &str) -> bool { + str.contains("/*") || str.contains("*/") +} + pub(crate) trait MacroParse { /// Parse the contents of an object into our AST, with a context if necessary. /// diff --git a/crates/macro/ui-tests/dup-fn-attrs.rs b/crates/macro/ui-tests/dup-fn-attrs.rs new file mode 100644 index 00000000000..c52b20b0f36 --- /dev/null +++ b/crates/macro/ui-tests/dup-fn-attrs.rs @@ -0,0 +1,31 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn fn_with_dup_attr1( + #[wasm_bindgen(js_name = "firstArg", js_name = "anotherFirstArg")] arg1: u32, +) -> JsValue { + arg1.into() +} + +#[wasm_bindgen] +pub async fn fn_with_dup_attr2( + #[wasm_bindgen(unchecked_param_type = "number", unchecked_param_type = "bigint")] arg1: u32, +) -> JsValue { + arg1.into() +} + +#[wasm_bindgen] +pub struct A {} + +#[wasm_bindgen] +impl A { + #[wasm_bindgen] + pub async fn fn_with_dup_attr3( + #[wasm_bindgen(param_description = "some number")] + #[wasm_bindgen(param_description = "some other description")] arg1: u32, + ) -> JsValue { + arg1.into() + } +} + +fn main() {} diff --git a/crates/macro/ui-tests/dup-fn-attrs.stderr b/crates/macro/ui-tests/dup-fn-attrs.stderr new file mode 100644 index 00000000000..09658cd09a0 --- /dev/null +++ b/crates/macro/ui-tests/dup-fn-attrs.stderr @@ -0,0 +1,17 @@ +error: unused wasm_bindgen attribute + --> ui-tests/dup-fn-attrs.rs:5:42 + | +5 | #[wasm_bindgen(js_name = "firstArg", js_name = "anotherFirstArg")] arg1: u32, + | ^^^^^^^ + +error: unused wasm_bindgen attribute + --> ui-tests/dup-fn-attrs.rs:12:53 + | +12 | #[wasm_bindgen(unchecked_param_type = "number", unchecked_param_type = "bigint")] arg1: u32, + | ^^^^^^^^^^^^^^^^^^^^ + +error: unused wasm_bindgen attribute + --> ui-tests/dup-fn-attrs.rs:25:24 + | +25 | #[wasm_bindgen(param_description = "some other description")] arg1: u32, + | ^^^^^^^^^^^^^^^^^ diff --git a/crates/macro/ui-tests/illegal-char-fn-attrs.rs b/crates/macro/ui-tests/illegal-char-fn-attrs.rs new file mode 100644 index 00000000000..d14af1256de --- /dev/null +++ b/crates/macro/ui-tests/illegal-char-fn-attrs.rs @@ -0,0 +1,34 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn fn_with_illegal_char_attr1( + #[wasm_bindgen(js_name = "/*firstArg")] arg1: u32, +) -> JsValue { + arg1.into() +} + +#[wasm_bindgen] +pub async fn fn_with_illegal_char_attr2( + #[wasm_bindgen(unchecked_param_type = "number*/")] arg1: u32, +) -> JsValue { + arg1.into() +} + +#[wasm_bindgen] +pub async fn fn_with_illegal_char_attr3( + #[wasm_bindgen(param_description = "/* some description */")] arg1: u32, +) -> JsValue { + arg1.into() +} + +#[wasm_bindgen(return_description = "*/ some description")] +pub async fn fn_with_illegal_char_attr4(arg1: u32) -> JsValue { + arg1.into() +} + +#[wasm_bindgen(unchecked_return_type = "*/ number")] +pub async fn fn_with_illegal_char_attr5(arg1: u32) -> JsValue { + arg1.into() +} + +fn main() {} diff --git a/crates/macro/ui-tests/illegal-char-fn-attrs.stderr b/crates/macro/ui-tests/illegal-char-fn-attrs.stderr new file mode 100644 index 00000000000..159cea4a3f4 --- /dev/null +++ b/crates/macro/ui-tests/illegal-char-fn-attrs.stderr @@ -0,0 +1,29 @@ +error: contains illegal chars + --> ui-tests/illegal-char-fn-attrs.rs:5:30 + | +5 | #[wasm_bindgen(js_name = "/*firstArg")] arg1: u32, + | ^^^^^^^^^^^^ + +error: contains illegal chars + --> ui-tests/illegal-char-fn-attrs.rs:12:43 + | +12 | #[wasm_bindgen(unchecked_param_type = "number*/")] arg1: u32, + | ^^^^^^^^^^ + +error: contains illegal chars + --> ui-tests/illegal-char-fn-attrs.rs:19:40 + | +19 | #[wasm_bindgen(param_description = "/* some description */")] arg1: u32, + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: contains illegal chars + --> ui-tests/illegal-char-fn-attrs.rs:24:37 + | +24 | #[wasm_bindgen(return_description = "*/ some description")] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: contains illegal chars + --> ui-tests/illegal-char-fn-attrs.rs:29:40 + | +29 | #[wasm_bindgen(unchecked_return_type = "*/ number")] + | ^^^^^^^^^^^ diff --git a/crates/macro/ui-tests/no-ret-fn-attr.rs b/crates/macro/ui-tests/no-ret-fn-attr.rs new file mode 100644 index 00000000000..afd2d764f31 --- /dev/null +++ b/crates/macro/ui-tests/no-ret-fn-attr.rs @@ -0,0 +1,21 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(unchecked_return_type = "number")] +pub fn no_ret_fn1() {} + +#[wasm_bindgen(return_description = "some description")] +pub async fn no_ret_fn2() {} + +#[wasm_bindgen] +pub struct A {} + +#[wasm_bindgen] +impl A { + #[wasm_bindgen(unchecked_return_type = "number")] + pub async fn no_ret_method1() {} + + #[wasm_bindgen(return_description = "some description")] + pub fn no_ret_method2() {} +} + +fn main() {} diff --git a/crates/macro/ui-tests/no-ret-fn-attr.stderr b/crates/macro/ui-tests/no-ret-fn-attr.stderr new file mode 100644 index 00000000000..74192856681 --- /dev/null +++ b/crates/macro/ui-tests/no-ret-fn-attr.stderr @@ -0,0 +1,23 @@ +error: cannot specify type for a function that doesn't return + --> ui-tests/no-ret-fn-attr.rs:3:40 + | +3 | #[wasm_bindgen(unchecked_return_type = "number")] + | ^^^^^^^^ + +error: cannot specify description for a function that doesn't return + --> ui-tests/no-ret-fn-attr.rs:6:37 + | +6 | #[wasm_bindgen(return_description = "some description")] + | ^^^^^^^^^^^^^^^^^^ + +error: cannot specify type for a function that doesn't return + --> ui-tests/no-ret-fn-attr.rs:14:44 + | +14 | #[wasm_bindgen(unchecked_return_type = "number")] + | ^^^^^^^^ + +error: cannot specify description for a function that doesn't return + --> ui-tests/no-ret-fn-attr.rs:17:41 + | +17 | #[wasm_bindgen(return_description = "some description")] + | ^^^^^^^^^^^^^^^^^^ diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index a032005ee09..50dab6b206a 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -135,22 +135,19 @@ macro_rules! shared_api { } struct Function<'a> { - arg_names: Vec, + args: Vec, asyncness: bool, name: &'a str, generate_typescript: bool, generate_jsdoc: bool, variadic: bool, - fn_attrs: Option, + ret_ty_override: Option, + ret_desc: Option, } - struct FunctionAttributes { - ret: FunctionComponentAttributes, - args: Vec, - } - - struct FunctionComponentAttributes { - ty: Option, + struct FunctionArgumentData { + name: String, + ty_override: Option, desc: Option, } diff --git a/crates/shared/src/schema_hash_approval.rs b/crates/shared/src/schema_hash_approval.rs index 9836a17fef7..36221d8e7d7 100644 --- a/crates/shared/src/schema_hash_approval.rs +++ b/crates/shared/src/schema_hash_approval.rs @@ -8,7 +8,7 @@ // If the schema in this library has changed then: // 1. Bump the version in `crates/shared/Cargo.toml` // 2. Change the `SCHEMA_VERSION` in this library to this new Cargo.toml version -const APPROVED_SCHEMA_FILE_HASH: &str = "10466730773526925892"; +const APPROVED_SCHEMA_FILE_HASH: &str = "3398053298395574572"; #[test] fn schema_version() { From bd11aab3fd5ca39876c7cc9c3153c3c33b2b410b Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 9 Jan 2025 00:13:10 +0000 Subject: [PATCH 26/50] fix --- crates/macro-support/src/parser.rs | 26 +++++++++---------- .../ui-tests/illegal-char-fn-attrs.stderr | 10 +++---- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index b87504a10c3..06fca153fc0 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -1145,14 +1145,14 @@ fn function_from_decl( "colliades with js/ts keyword", )); } - if contains_js_comment_syntax(ty) { - return Err(Diagnostic::span_error(*span, "contains illegal chars")); + if contains_js_comment_close(ty) { + return Err(Diagnostic::span_error(*span, "contains illegal comment close syntax")); } Ok(Some(ty.to_string())) })?, desc: desc.as_ref().map_or(Ok(None), |(desc, span)| { - if contains_js_comment_syntax(desc) { - return Err(Diagnostic::span_error(*span, "contains illegal chars")); + if contains_js_comment_close(desc) { + return Err(Diagnostic::span_error(*span, "contains illegal comment close syntax")); } Ok(Some(desc.to_string())) })?, @@ -1239,8 +1239,8 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn "colliades with js/ts keyword", )); } - if contains_js_comment_syntax(js_name_override) { - return Err(Diagnostic::span_error(span, "contains illegal chars")); + if contains_js_comment_close(js_name_override) { + return Err(Diagnostic::span_error(span, "contains illegal comment close syntax")); } Ok(Some(js_name_override.to_string())) })?, @@ -1253,16 +1253,16 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn "colliades with js/ts keyword", )); } - if contains_js_comment_syntax(ty) { - return Err(Diagnostic::span_error(span, "contains illegal chars")); + if contains_js_comment_close(ty) { + return Err(Diagnostic::span_error(span, "contains illegal comment close syntax")); } Ok(Some(ty.to_string())) })?, desc: attrs .param_description() .map_or(Ok(None), |(description, span)| { - if contains_js_comment_syntax(description) { - return Err(Diagnostic::span_error(span, "contains illegal chars")); + if contains_js_comment_close(description) { + return Err(Diagnostic::span_error(span, "contains illegal comment close syntax")); } Ok(Some(description.to_string())) })?, @@ -1274,9 +1274,9 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn Ok(args_attrs) } -/// Checks if the given string contains JS/TS comment block open/close syntax -fn contains_js_comment_syntax(str: &str) -> bool { - str.contains("/*") || str.contains("*/") +/// Checks if the given string contains JS/TS comment block close syntax +fn contains_js_comment_close(str: &str) -> bool { + str.contains("*/") } pub(crate) trait MacroParse { diff --git a/crates/macro/ui-tests/illegal-char-fn-attrs.stderr b/crates/macro/ui-tests/illegal-char-fn-attrs.stderr index 159cea4a3f4..8d5109939fd 100644 --- a/crates/macro/ui-tests/illegal-char-fn-attrs.stderr +++ b/crates/macro/ui-tests/illegal-char-fn-attrs.stderr @@ -1,28 +1,28 @@ -error: contains illegal chars +error: contains illegal comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:5:30 | 5 | #[wasm_bindgen(js_name = "/*firstArg")] arg1: u32, | ^^^^^^^^^^^^ -error: contains illegal chars +error: contains illegal comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:12:43 | 12 | #[wasm_bindgen(unchecked_param_type = "number*/")] arg1: u32, | ^^^^^^^^^^ -error: contains illegal chars +error: contains illegal comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:19:40 | 19 | #[wasm_bindgen(param_description = "/* some description */")] arg1: u32, | ^^^^^^^^^^^^^^^^^^^^^^^^ -error: contains illegal chars +error: contains illegal comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:24:37 | 24 | #[wasm_bindgen(return_description = "*/ some description")] | ^^^^^^^^^^^^^^^^^^^^^ -error: contains illegal chars +error: contains illegal comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:29:40 | 29 | #[wasm_bindgen(unchecked_return_type = "*/ number")] From 9d167f06ca79af3a3b4f471378937f8820135980 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 9 Jan 2025 00:15:37 +0000 Subject: [PATCH 27/50] fmt --- crates/macro-support/src/parser.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 06fca153fc0..452d323b038 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -1146,13 +1146,19 @@ fn function_from_decl( )); } if contains_js_comment_close(ty) { - return Err(Diagnostic::span_error(*span, "contains illegal comment close syntax")); + return Err(Diagnostic::span_error( + *span, + "contains illegal comment close syntax", + )); } Ok(Some(ty.to_string())) })?, desc: desc.as_ref().map_or(Ok(None), |(desc, span)| { if contains_js_comment_close(desc) { - return Err(Diagnostic::span_error(*span, "contains illegal comment close syntax")); + return Err(Diagnostic::span_error( + *span, + "contains illegal comment close syntax", + )); } Ok(Some(desc.to_string())) })?, @@ -1240,7 +1246,10 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn )); } if contains_js_comment_close(js_name_override) { - return Err(Diagnostic::span_error(span, "contains illegal comment close syntax")); + return Err(Diagnostic::span_error( + span, + "contains illegal comment close syntax", + )); } Ok(Some(js_name_override.to_string())) })?, @@ -1254,7 +1263,10 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn )); } if contains_js_comment_close(ty) { - return Err(Diagnostic::span_error(span, "contains illegal comment close syntax")); + return Err(Diagnostic::span_error( + span, + "contains illegal comment close syntax", + )); } Ok(Some(ty.to_string())) })?, @@ -1262,7 +1274,10 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn .param_description() .map_or(Ok(None), |(description, span)| { if contains_js_comment_close(description) { - return Err(Diagnostic::span_error(span, "contains illegal comment close syntax")); + return Err(Diagnostic::span_error( + span, + "contains illegal comment close syntax", + )); } Ok(Some(description.to_string())) })?, From 0a598c4e376616ca6e38e58421d40697fd3530dc Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 9 Jan 2025 00:17:16 +0000 Subject: [PATCH 28/50] typo --- crates/macro-support/src/parser.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 452d323b038..bde08e02ee8 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -1142,7 +1142,7 @@ fn function_from_decl( if is_js_keyword(ty) { return Err(Diagnostic::span_error( *span, - "colliades with js/ts keyword", + "collides with js/ts keyword", )); } if contains_js_comment_close(ty) { @@ -1242,7 +1242,7 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn if is_js_keyword(js_name_override) { return Err(Diagnostic::span_error( span, - "colliades with js/ts keyword", + "collides with js/ts keyword", )); } if contains_js_comment_close(js_name_override) { @@ -1259,7 +1259,7 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn if is_js_keyword(ty) { return Err(Diagnostic::span_error( span, - "colliades with js/ts keyword", + "collides with js/ts keyword", )); } if contains_js_comment_close(ty) { From 2ea857e7e9a65fb5b57baee404d500474c7fbcac Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 9 Jan 2025 00:17:42 +0000 Subject: [PATCH 29/50] fmt --- crates/macro-support/src/parser.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index bde08e02ee8..321fc9cfc88 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -1140,10 +1140,7 @@ fn function_from_decl( r#type: replace_self(*ty), ty_override: ty_override.as_ref().map_or(Ok(None), |(ty, span)| { if is_js_keyword(ty) { - return Err(Diagnostic::span_error( - *span, - "collides with js/ts keyword", - )); + return Err(Diagnostic::span_error(*span, "collides with js/ts keyword")); } if contains_js_comment_close(ty) { return Err(Diagnostic::span_error( From 227a60b57f67874f8f4779a0c11fede882e5fb2f Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 9 Jan 2025 00:22:15 +0000 Subject: [PATCH 30/50] fix test --- crates/macro/ui-tests/illegal-char-fn-attrs.rs | 6 +++--- crates/macro/ui-tests/illegal-char-fn-attrs.stderr | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/macro/ui-tests/illegal-char-fn-attrs.rs b/crates/macro/ui-tests/illegal-char-fn-attrs.rs index d14af1256de..d7213c56fa7 100644 --- a/crates/macro/ui-tests/illegal-char-fn-attrs.rs +++ b/crates/macro/ui-tests/illegal-char-fn-attrs.rs @@ -2,14 +2,14 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn fn_with_illegal_char_attr1( - #[wasm_bindgen(js_name = "/*firstArg")] arg1: u32, + #[wasm_bindgen(js_name = "abcd */firstArg")] arg1: u32, ) -> JsValue { arg1.into() } #[wasm_bindgen] pub async fn fn_with_illegal_char_attr2( - #[wasm_bindgen(unchecked_param_type = "number*/")] arg1: u32, + #[wasm_bindgen(unchecked_param_type = "num*/ber")] arg1: u32, ) -> JsValue { arg1.into() } @@ -26,7 +26,7 @@ pub async fn fn_with_illegal_char_attr4(arg1: u32) -> JsValue { arg1.into() } -#[wasm_bindgen(unchecked_return_type = "*/ number")] +#[wasm_bindgen(unchecked_return_type = "number */ abcd")] pub async fn fn_with_illegal_char_attr5(arg1: u32) -> JsValue { arg1.into() } diff --git a/crates/macro/ui-tests/illegal-char-fn-attrs.stderr b/crates/macro/ui-tests/illegal-char-fn-attrs.stderr index 8d5109939fd..3d90faba3d8 100644 --- a/crates/macro/ui-tests/illegal-char-fn-attrs.stderr +++ b/crates/macro/ui-tests/illegal-char-fn-attrs.stderr @@ -1,13 +1,13 @@ error: contains illegal comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:5:30 | -5 | #[wasm_bindgen(js_name = "/*firstArg")] arg1: u32, - | ^^^^^^^^^^^^ +5 | #[wasm_bindgen(js_name = "abcd */firstArg")] arg1: u32, + | ^^^^^^^^^^^^^^^^^ error: contains illegal comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:12:43 | -12 | #[wasm_bindgen(unchecked_param_type = "number*/")] arg1: u32, +12 | #[wasm_bindgen(unchecked_param_type = "num*/ber")] arg1: u32, | ^^^^^^^^^^ error: contains illegal comment close syntax @@ -25,5 +25,5 @@ error: contains illegal comment close syntax error: contains illegal comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:29:40 | -29 | #[wasm_bindgen(unchecked_return_type = "*/ number")] - | ^^^^^^^^^^^ +29 | #[wasm_bindgen(unchecked_return_type = "number */ abcd")] + | ^^^^^^^^^^^^^^^^ From 185f21bcf6c60c0cbefbf1363af490acb6e2b583 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 9 Jan 2025 00:43:07 +0000 Subject: [PATCH 31/50] typo --- .../attributes/on-rust-exports/function-attributes.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/guide/src/reference/attributes/on-rust-exports/function-attributes.md b/guide/src/reference/attributes/on-rust-exports/function-attributes.md index 021de749d10..0225cc7586c 100644 --- a/guide/src/reference/attributes/on-rust-exports/function-attributes.md +++ b/guide/src/reference/attributes/on-rust-exports/function-attributes.md @@ -1,13 +1,13 @@ # `function-attributes` -By default, exported Rust functions and methods generate function signature from equivalent Rust types identifiers without any arguments and return variable documentations. However by using function attributes, it's possible to override a function's return type and arguments names and types for generated bindings as well as the ability to write specific documentation for each of them individually as desired: +By default, exported Rust functions and methods generate function signature from equivalent Rust types identifiers without any arguments and return variable documentations. However by using function attributes, it's possible to override a function's return type and arguments names and types for generated bindings, as well as the ability to write specific documentation for each of them individually as desired: - `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(return_description)]` used to override a function's return type and to specify descriptions for generated JS/TS bindings. - `#[wasm_bindgen(js_name)]`, `#[wasm_bindgen(unchecked_param_type)]` and `#[wasm_bindgen(param_description)]` applied to a Rust function argument to override that argument's name and type and to specify descriptions for generated JS/TS bindings. > **NOTE**: > Types that are provided using `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(unchecked_param_type)]` aren't checked as these attributes' identifiers entail, meaning they will end up in the function's signature and docs bindings exactly as they have been specified and there are no checks/validation for them in place, so only because a user uses `#[wasm_bindgen(unchecked_param_type = "number")]` for example, it doesn't necessarily mean it's actually going to be a value of number type, therefore validation and checks between the value and its type should be handled by the user and the responsibility of using them correctly and carefully relies solely on the user. -Let's look at some more exmaples in details: +Let's look at some exmaples: ```rust /// Description for foo #[wasm_bindgen(unchecked_return_type = "Foo", return_description = "some description for return type")] @@ -20,6 +20,7 @@ pub async fn foo( // function body } ``` + This will generate the following JS bindings: ```js /** @@ -30,6 +31,7 @@ This will generate the following JS bindings: */ export function foo(firstArg, secondArg) {}; ``` + And will generate the following TS bindings: ```ts /** @@ -41,7 +43,7 @@ And will generate the following TS bindings: export function foo(firstArg: string, secondArg: Bar): Promise; ``` -Same thing applies to Rust struct's (and enums) impl methods and their equivalent JS/TS/TS class methods: +Same thing applies to Rust struct's (and enums) impl methods and their equivalent JS/TS class methods: ```rust /// Description for Foo #[wasm_bindgen] @@ -97,4 +99,4 @@ export class Foo { } ``` -As shown in these examples, these attributes allows for a great level of control and customization over generated bindings. But note that they can only be used on functions and methods that are being exported to JS/TS/TS and cannot be used on the `self` argument of Rust struct/enum methods. +As shown in these examples, these attributes provide a great level of control and customization over generated bindings. But note that they can only be used on functions and methods that are being exported to JS/TS and cannot be used on the `self` argument of Rust struct/enum methods. From e7f2618b0d93236edb8468bf4210fbe301a21387 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 9 Jan 2025 11:02:55 +0100 Subject: [PATCH 32/50] Add ability to specific attributes that error when unused --- crates/macro-support/src/parser.rs | 114 ++++--- .../macro/ui-tests/unused-attributes.stderr | 304 ++++++++++++------ 2 files changed, 273 insertions(+), 145 deletions(-) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 321fc9cfc88..b5c3be4c727 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -107,7 +107,12 @@ fn is_non_value_js_keyword(keyword: &str) -> bool { struct AttributeParseState { parsed: Cell, checks: Cell, - unused_attrs: RefCell>, + unused_attrs: RefCell>, +} + +struct UnusedState { + error: bool, + ident: Ident, } /// Parsed attributes from a `#[wasm_bindgen(..)]`. @@ -128,57 +133,57 @@ pub struct JsNamespace(Vec); macro_rules! attrgen { ($mac:ident) => { $mac! { - (catch, Catch(Span)), - (constructor, Constructor(Span)), - (method, Method(Span)), - (static_method_of, StaticMethodOf(Span, Ident)), - (js_namespace, JsNamespace(Span, JsNamespace, Vec)), - (module, Module(Span, String, Span)), - (raw_module, RawModule(Span, String, Span)), - (inline_js, InlineJs(Span, String, Span)), - (getter, Getter(Span, Option)), - (setter, Setter(Span, Option)), - (indexing_getter, IndexingGetter(Span)), - (indexing_setter, IndexingSetter(Span)), - (indexing_deleter, IndexingDeleter(Span)), - (structural, Structural(Span)), - (r#final, Final(Span)), - (readonly, Readonly(Span)), - (js_name, JsName(Span, String, Span)), - (js_class, JsClass(Span, String, Span)), - (inspectable, Inspectable(Span)), - (is_type_of, IsTypeOf(Span, syn::Expr)), - (extends, Extends(Span, syn::Path)), - (no_deref, NoDeref(Span)), - (vendor_prefix, VendorPrefix(Span, Ident)), - (variadic, Variadic(Span)), - (typescript_custom_section, TypescriptCustomSection(Span)), - (skip_typescript, SkipTypescript(Span)), - (skip_jsdoc, SkipJsDoc(Span)), - (main, Main(Span)), - (start, Start(Span)), - (wasm_bindgen, WasmBindgen(Span, syn::Path)), - (js_sys, JsSys(Span, syn::Path)), - (wasm_bindgen_futures, WasmBindgenFutures(Span, syn::Path)), - (skip, Skip(Span)), - (typescript_type, TypeScriptType(Span, String, Span)), - (getter_with_clone, GetterWithClone(Span)), - (static_string, StaticString(Span)), - (thread_local, ThreadLocal(Span)), - (thread_local_v2, ThreadLocalV2(Span)), - (unchecked_return_type, ReturnType(Span, String, Span)), - (return_description, ReturnDesc(Span, String, Span)), - (unchecked_param_type, ParamType(Span, String, Span)), - (param_description, ParamDesc(Span, String, Span)), + (catch, false, Catch(Span)), + (constructor, false, Constructor(Span)), + (method, false, Method(Span)), + (static_method_of, false, StaticMethodOf(Span, Ident)), + (js_namespace, false, JsNamespace(Span, JsNamespace, Vec)), + (module, false, Module(Span, String, Span)), + (raw_module, false, RawModule(Span, String, Span)), + (inline_js, false, InlineJs(Span, String, Span)), + (getter, false, Getter(Span, Option)), + (setter, false, Setter(Span, Option)), + (indexing_getter, false, IndexingGetter(Span)), + (indexing_setter, false, IndexingSetter(Span)), + (indexing_deleter, false, IndexingDeleter(Span)), + (structural, false, Structural(Span)), + (r#final, false, Final(Span)), + (readonly, false, Readonly(Span)), + (js_name, false, JsName(Span, String, Span)), + (js_class, false, JsClass(Span, String, Span)), + (inspectable, false, Inspectable(Span)), + (is_type_of, false, IsTypeOf(Span, syn::Expr)), + (extends, false, Extends(Span, syn::Path)), + (no_deref, false, NoDeref(Span)), + (vendor_prefix, false, VendorPrefix(Span, Ident)), + (variadic, false, Variadic(Span)), + (typescript_custom_section, false, TypescriptCustomSection(Span)), + (skip_typescript, false, SkipTypescript(Span)), + (skip_jsdoc, false, SkipJsDoc(Span)), + (main, false, Main(Span)), + (start, false, Start(Span)), + (wasm_bindgen, false, WasmBindgen(Span, syn::Path)), + (js_sys, false, JsSys(Span, syn::Path)), + (wasm_bindgen_futures, false, WasmBindgenFutures(Span, syn::Path)), + (skip, false, Skip(Span)), + (typescript_type, false, TypeScriptType(Span, String, Span)), + (getter_with_clone, false, GetterWithClone(Span)), + (static_string, false, StaticString(Span)), + (thread_local, false, ThreadLocal(Span)), + (thread_local_v2, false, ThreadLocalV2(Span)), + (unchecked_return_type, true, ReturnType(Span, String, Span)), + (return_description, true, ReturnDesc(Span, String, Span)), + (unchecked_param_type, true, ParamType(Span, String, Span)), + (param_description, true, ParamDesc(Span, String, Span)), // For testing purposes only. - (assert_no_shim, AssertNoShim(Span)), + (assert_no_shim, false, AssertNoShim(Span)), } }; } macro_rules! methods { - ($(($name:ident, $variant:ident($($contents:tt)*)),)*) => { + ($(($name:ident, $invalid_unused:literal, $variant:ident($($contents:tt)*)),)*) => { $(methods!(@method $name, $variant($($contents)*));)* fn enforce_used(self) -> Result<(), Diagnostic> { @@ -210,7 +215,10 @@ macro_rules! methods { .map(|attr| { match attr { $(BindgenAttr::$variant(span, ..) => { - syn::parse_quote_spanned!(*span => $name) + UnusedState { + error: $invalid_unused, + ident: syn::parse_quote_spanned!(*span => $name) + } })* } }) @@ -354,7 +362,7 @@ impl Parse for BindgenAttrs { } macro_rules! gen_bindgen_attr { - ($(($method:ident, $($variants:tt)*),)*) => { + ($(($method:ident, $_:literal, $($variants:tt)*),)*) => { /// The possible attributes in the `#[wasm_bindgen]`. #[cfg_attr(feature = "extra-traits", derive(Debug))] pub enum BindgenAttr { @@ -374,7 +382,7 @@ impl Parse for BindgenAttr { let raw_attr_string = format!("r#{}", attr_string); macro_rules! parsers { - ($(($name:ident, $($contents:tt)*),)*) => { + ($(($name:ident, $_:literal, $($contents:tt)*),)*) => { $( if attr_string == stringify!($name) || raw_attr_string == stringify!($name) { parsers!( @@ -2206,10 +2214,18 @@ pub fn check_unused_attrs(tokens: &mut TokenStream) { assert_eq!(state.parsed.get(), state.checks.get()); let unused_attrs = &*state.unused_attrs.borrow(); if !unused_attrs.is_empty() { + let unused_attrs = unused_attrs.iter().map(|UnusedState { error, ident }| { + if *error { + let text = format!("invalid attribute {ident} in this position"); + quote::quote! { ::core::compile_error!(#text); } + } else { + quote::quote! { let #ident: (); } + } + }); tokens.extend(quote::quote! { // Anonymous scope to prevent name clashes. const _: () = { - #(let #unused_attrs: ();)* + #(#unused_attrs)* }; }); } diff --git a/crates/macro/ui-tests/unused-attributes.stderr b/crates/macro/ui-tests/unused-attributes.stderr index 3e1786c3da5..26dc2accd3f 100644 --- a/crates/macro/ui-tests/unused-attributes.stderr +++ b/crates/macro/ui-tests/unused-attributes.stderr @@ -1,3 +1,211 @@ +error: invalid attribute unchecked_return_type in this position + --> ui-tests/unused-attributes.rs:32:1 + | +32 | / #[wasm_bindgen( +33 | | unchecked_return_type = "something", +34 | | return_description = "something", +35 | | unchecked_param_type = "something", +36 | | param_description = "somthing" +37 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute return_description in this position + --> ui-tests/unused-attributes.rs:32:1 + | +32 | / #[wasm_bindgen( +33 | | unchecked_return_type = "something", +34 | | return_description = "something", +35 | | unchecked_param_type = "something", +36 | | param_description = "somthing" +37 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_param_type in this position + --> ui-tests/unused-attributes.rs:32:1 + | +32 | / #[wasm_bindgen( +33 | | unchecked_return_type = "something", +34 | | return_description = "something", +35 | | unchecked_param_type = "something", +36 | | param_description = "somthing" +37 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute param_description in this position + --> ui-tests/unused-attributes.rs:32:1 + | +32 | / #[wasm_bindgen( +33 | | unchecked_return_type = "something", +34 | | return_description = "something", +35 | | unchecked_param_type = "something", +36 | | param_description = "somthing" +37 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_return_type in this position + --> ui-tests/unused-attributes.rs:40:1 + | +40 | / #[wasm_bindgen( +41 | | unchecked_return_type = "something", +42 | | return_description = "something", +43 | | unchecked_param_type = "something", +44 | | param_description = "somthing" +45 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute return_description in this position + --> ui-tests/unused-attributes.rs:40:1 + | +40 | / #[wasm_bindgen( +41 | | unchecked_return_type = "something", +42 | | return_description = "something", +43 | | unchecked_param_type = "something", +44 | | param_description = "somthing" +45 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_param_type in this position + --> ui-tests/unused-attributes.rs:40:1 + | +40 | / #[wasm_bindgen( +41 | | unchecked_return_type = "something", +42 | | return_description = "something", +43 | | unchecked_param_type = "something", +44 | | param_description = "somthing" +45 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute param_description in this position + --> ui-tests/unused-attributes.rs:40:1 + | +40 | / #[wasm_bindgen( +41 | | unchecked_return_type = "something", +42 | | return_description = "something", +43 | | unchecked_param_type = "something", +44 | | param_description = "somthing" +45 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_return_type in this position + --> ui-tests/unused-attributes.rs:51:1 + | +51 | / #[wasm_bindgen( +52 | | unchecked_return_type = "something", +53 | | return_description = "something", +54 | | unchecked_param_type = "something", +55 | | param_description = "somthing" +56 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute return_description in this position + --> ui-tests/unused-attributes.rs:51:1 + | +51 | / #[wasm_bindgen( +52 | | unchecked_return_type = "something", +53 | | return_description = "something", +54 | | unchecked_param_type = "something", +55 | | param_description = "somthing" +56 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_param_type in this position + --> ui-tests/unused-attributes.rs:51:1 + | +51 | / #[wasm_bindgen( +52 | | unchecked_return_type = "something", +53 | | return_description = "something", +54 | | unchecked_param_type = "something", +55 | | param_description = "somthing" +56 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute param_description in this position + --> ui-tests/unused-attributes.rs:51:1 + | +51 | / #[wasm_bindgen( +52 | | unchecked_return_type = "something", +53 | | return_description = "something", +54 | | unchecked_param_type = "something", +55 | | param_description = "somthing" +56 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_return_type in this position + --> ui-tests/unused-attributes.rs:61:1 + | +61 | / #[wasm_bindgen( +62 | | unchecked_return_type = "something", +63 | | return_description = "something", +64 | | unchecked_param_type = "something", +65 | | param_description = "somthing" +66 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute return_description in this position + --> ui-tests/unused-attributes.rs:61:1 + | +61 | / #[wasm_bindgen( +62 | | unchecked_return_type = "something", +63 | | return_description = "something", +64 | | unchecked_param_type = "something", +65 | | param_description = "somthing" +66 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_param_type in this position + --> ui-tests/unused-attributes.rs:61:1 + | +61 | / #[wasm_bindgen( +62 | | unchecked_return_type = "something", +63 | | return_description = "something", +64 | | unchecked_param_type = "something", +65 | | param_description = "somthing" +66 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute param_description in this position + --> ui-tests/unused-attributes.rs:61:1 + | +61 | / #[wasm_bindgen( +62 | | unchecked_return_type = "something", +63 | | return_description = "something", +64 | | unchecked_param_type = "something", +65 | | param_description = "somthing" +66 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + error: unused variable: `method` --> ui-tests/unused-attributes.rs:9:20 | @@ -39,99 +247,3 @@ error: unused variable: `final` | 24 | #[wasm_bindgen(getter_with_clone, final)] | ^^^^^ help: if this is intentional, prefix it with an underscore: `_final` - -error: unused variable: `unchecked_return_type` - --> ui-tests/unused-attributes.rs:33:5 - | -33 | unchecked_return_type = "something", - | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` - -error: unused variable: `return_description` - --> ui-tests/unused-attributes.rs:34:5 - | -34 | return_description = "something", - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` - -error: unused variable: `unchecked_param_type` - --> ui-tests/unused-attributes.rs:35:5 - | -35 | unchecked_param_type = "something", - | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` - -error: unused variable: `param_description` - --> ui-tests/unused-attributes.rs:36:5 - | -36 | param_description = "somthing" - | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` - -error: unused variable: `unchecked_return_type` - --> ui-tests/unused-attributes.rs:41:5 - | -41 | unchecked_return_type = "something", - | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` - -error: unused variable: `return_description` - --> ui-tests/unused-attributes.rs:42:5 - | -42 | return_description = "something", - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` - -error: unused variable: `unchecked_param_type` - --> ui-tests/unused-attributes.rs:43:5 - | -43 | unchecked_param_type = "something", - | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` - -error: unused variable: `param_description` - --> ui-tests/unused-attributes.rs:44:5 - | -44 | param_description = "somthing" - | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` - -error: unused variable: `unchecked_return_type` - --> ui-tests/unused-attributes.rs:52:5 - | -52 | unchecked_return_type = "something", - | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` - -error: unused variable: `return_description` - --> ui-tests/unused-attributes.rs:53:5 - | -53 | return_description = "something", - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` - -error: unused variable: `unchecked_param_type` - --> ui-tests/unused-attributes.rs:54:5 - | -54 | unchecked_param_type = "something", - | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` - -error: unused variable: `param_description` - --> ui-tests/unused-attributes.rs:55:5 - | -55 | param_description = "somthing" - | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` - -error: unused variable: `unchecked_return_type` - --> ui-tests/unused-attributes.rs:62:5 - | -62 | unchecked_return_type = "something", - | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_return_type` - -error: unused variable: `return_description` - --> ui-tests/unused-attributes.rs:63:5 - | -63 | return_description = "something", - | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_return_description` - -error: unused variable: `unchecked_param_type` - --> ui-tests/unused-attributes.rs:64:5 - | -64 | unchecked_param_type = "something", - | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unchecked_param_type` - -error: unused variable: `param_description` - --> ui-tests/unused-attributes.rs:65:5 - | -65 | param_description = "somthing" - | ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_param_description` From 099242624c4b94e61f2805104a88e6321fa4700f Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 9 Jan 2025 11:14:29 +0100 Subject: [PATCH 33/50] Respect MSRV --- crates/macro-support/src/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index b5c3be4c727..250ef93dd21 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -2216,7 +2216,7 @@ pub fn check_unused_attrs(tokens: &mut TokenStream) { if !unused_attrs.is_empty() { let unused_attrs = unused_attrs.iter().map(|UnusedState { error, ident }| { if *error { - let text = format!("invalid attribute {ident} in this position"); + let text = format!("invalid attribute {} in this position", ident); quote::quote! { ::core::compile_error!(#text); } } else { quote::quote! { let #ident: (); } From 2e8e6c147612dd755408ed79ca4231242f0601b3 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 9 Jan 2025 19:25:28 +0000 Subject: [PATCH 34/50] apply minor requested changes --- crates/backend/src/ast.rs | 12 ++-- crates/backend/src/encode.rs | 44 ++++++------ crates/cli-support/src/decode.rs | 21 +----- crates/cli-support/src/js/binding.rs | 71 ++++++++++--------- crates/cli-support/src/js/mod.rs | 6 +- crates/cli-support/src/wit/mod.rs | 18 ++++- crates/cli-support/src/wit/nonstandard.rs | 18 +++-- crates/macro-support/src/parser.rs | 12 ++-- crates/shared/src/lib.rs | 12 ++-- .../on-rust-exports/function-attributes.md | 2 +- 10 files changed, 110 insertions(+), 106 deletions(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index c0e8664d07e..0fed5f18c54 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -400,8 +400,8 @@ pub struct Function { pub struct FunctionReturnData { /// Specifies the type of the function's return pub r#type: syn::Type, - /// Specifies the return type override provided by attributes - pub ty_override: Option, + /// Specifies the JS return type override + pub js_type: Option, /// Specifies the return description pub desc: Option, } @@ -410,12 +410,12 @@ pub struct FunctionReturnData { #[cfg_attr(feature = "extra-traits", derive(Debug))] #[derive(Clone)] pub struct FunctionArgumentData { - /// Specifies the argument name - pub js_name: Option, /// Specifies the type of the function's argument pub pat_type: syn::PatType, - /// Specifies the argument type override provided by attributes - pub ty_override: Option, + /// Specifies the JS argument name override + pub js_name: Option, + /// Specifies the JS function argument type override + pub js_type: Option, /// Specifies the argument description pub desc: Option, } diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index 39de7963a00..8d55306b5ba 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -215,28 +215,24 @@ fn shared_export<'a>( } fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Function<'a> { - let args = func - .arguments - .iter() - .enumerate() - .map(|(idx, arg)| FunctionArgumentData { - // use argument's "js_name" if it was provided in attributes - name: if let Some(name) = &arg.js_name { - name.clone() - } else if let syn::Pat::Ident(x) = &*arg.pat_type.pat { - x.ident.unraw().to_string() - } else { - format!("arg{}", idx) - }, - ty_override: arg.ty_override.clone(), - desc: arg.desc.clone(), - }) - .collect::>(); - let (ret_ty_override, ret_desc) = func - .ret - .as_ref() - .map(|ret| (ret.ty_override.clone(), ret.desc.clone())) - .unwrap_or((None, None)); + let args = + func.arguments + .iter() + .enumerate() + .map(|(idx, arg)| FunctionArgumentData { + // use argument's "js_name" if it was provided in attributes + name: arg.js_name.clone().unwrap_or( + if let syn::Pat::Ident(x) = &*arg.pat_type.pat { + x.ident.unraw().to_string() + } else { + format!("arg{}", idx) + }, + ), + ty_override: arg.js_type.as_deref(), + desc: arg.desc.as_deref(), + }) + .collect::>(); + Function { args, asyncness: func.r#async, @@ -244,8 +240,8 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi generate_typescript: func.generate_typescript, generate_jsdoc: func.generate_jsdoc, variadic: func.variadic, - ret_ty_override, - ret_desc, + ret_ty_override: func.ret.as_ref().and_then(|v| v.js_type.as_deref()), + ret_desc: func.ret.as_ref().and_then(|v| v.desc.as_deref()), } } diff --git a/crates/cli-support/src/decode.rs b/crates/cli-support/src/decode.rs index d0403b26f1f..f527acea2d2 100644 --- a/crates/cli-support/src/decode.rs +++ b/crates/cli-support/src/decode.rs @@ -1,4 +1,4 @@ -use std::{fmt::Debug, ops::Deref, str}; +use std::{ops::Deref, str}; pub trait Decode<'src>: Sized { fn decode(data: &mut &'src [u8]) -> Self; @@ -94,25 +94,6 @@ impl<'src, T: Decode<'src>> Decode<'src> for Option { } } -impl Clone for FunctionArgumentData { - fn clone(&self) -> Self { - Self { - name: self.name.clone(), - desc: self.desc.clone(), - ty_override: self.ty_override.clone(), - } - } -} - -impl Debug for FunctionArgumentData { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!( - "FunctionArgumentData {{ name: {:?}, ty_override: {:?}, desc: {:?} }}", - self.name, self.ty_override, self.desc - )) - } -} - macro_rules! decode_struct { ($name:ident ($($lt:tt)*) $($field:ident: $ty:ty,)*) => { pub struct $name <$($lt)*> { diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 88ffb7bfd56..66a1fa6500b 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -4,10 +4,11 @@ //! exported functions, table elements, imports, etc. All function shims //! generated by `wasm-bindgen` run through this type. -use crate::decode::FunctionArgumentData; use crate::js::Context; use crate::wit::InstructionData; -use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction}; +use crate::wit::{ + Adapter, AdapterId, AdapterKind, AdapterType, AuxFunctionArgumentData, Instruction, +}; use anyhow::{anyhow, bail, Error}; use std::collections::HashSet; use std::fmt::Write; @@ -122,7 +123,7 @@ impl<'a, 'b> Builder<'a, 'b> { &mut self, adapter: &Adapter, instructions: &[InstructionData], - args_data: &Option>, + args_data: &Option>, asyncness: bool, variadic: bool, generate_jsdoc: bool, @@ -164,7 +165,7 @@ impl<'a, 'b> Builder<'a, 'b> { for (i, param) in params.enumerate() { let arg = match args_data { Some(list) => list[i].clone(), - None => FunctionArgumentData { + None => AuxFunctionArgumentData { name: format!("arg{}", i), ty_override: None, desc: None, @@ -230,21 +231,29 @@ impl<'a, 'b> Builder<'a, 'b> { // } let mut code = String::new(); - let args_names = function_args - .iter() - .map(|v| v.name.as_str()) - .collect::>(); code.push('('); if variadic { - if let Some((last, non_variadic_args)) = args_names.split_last() { - code.push_str(&non_variadic_args.join(", ")); + if let Some((last, non_variadic_args)) = function_args.split_last() { + code.push_str( + &non_variadic_args + .iter() + .map(|v| v.name.as_str()) + .collect::>() + .join(", "), + ); if !non_variadic_args.is_empty() { code.push_str(", "); } - code.push_str((String::from("...") + last).as_str()) + code.push_str((String::from("...") + &last.name).as_str()) } } else { - code.push_str(&args_names.join(", ")); + code.push_str( + &function_args + .iter() + .map(|v| v.name.as_str()) + .collect::>() + .join(", "), + ); } code.push_str(") {\n"); @@ -328,7 +337,7 @@ impl<'a, 'b> Builder<'a, 'b> { /// return value, it doesn't include the function name in any way. fn typescript_signature( &self, - args_data: &[FunctionArgumentData], + args_data: &[AuxFunctionArgumentData], arg_tys: &[&AdapterType], result_tys: &[AdapterType], might_be_optional_field: &mut bool, @@ -342,7 +351,7 @@ impl<'a, 'b> Builder<'a, 'b> { let mut ts_arg_tys = Vec::new(); let mut ts_refs = HashSet::new(); for ( - FunctionArgumentData { + AuxFunctionArgumentData { name, ty_override, .. }, ty, @@ -432,7 +441,7 @@ impl<'a, 'b> Builder<'a, 'b> { /// and the return value. fn js_doc_comments( &self, - args_data: &[FunctionArgumentData], + args_data: &[AuxFunctionArgumentData], arg_tys: &[&AdapterType], ts_ret: &Option, variadic: bool, @@ -448,7 +457,7 @@ impl<'a, 'b> Builder<'a, 'b> { let mut js_doc_args = Vec::new(); for ( - FunctionArgumentData { + AuxFunctionArgumentData { name, ty_override, desc, @@ -492,7 +501,7 @@ impl<'a, 'b> Builder<'a, 'b> { let mut ret: String = js_doc_args.into_iter().rev().collect(); if let ( - Some(FunctionArgumentData { + Some(AuxFunctionArgumentData { name, ty_override, desc, @@ -534,33 +543,29 @@ impl<'a, 'b> Builder<'a, 'b> { /// the return value descriptions. fn ts_doc_comments( &self, - args_data: &[FunctionArgumentData], + args_data: &[AuxFunctionArgumentData], ret_desc: &Option, ) -> String { - let mut ts_doc_args = Vec::new(); + let mut ts_doc = String::new(); // ofc we dont need arg type for ts doc, only arg name - for FunctionArgumentData { name, desc, .. } in args_data.iter() { - let mut arg = "@param ".to_string(); - arg.push_str(name); + for AuxFunctionArgumentData { name, desc, .. } in args_data.iter() { + ts_doc.push_str("@param "); + ts_doc.push_str(name); // append desc if let Some(v) = desc { - arg.push_str(" - "); - arg.push_str(v); + ts_doc.push_str(" - "); + ts_doc.push_str(v); } - - arg.push('\n'); - ts_doc_args.push(arg); + ts_doc.push('\n'); } - let mut ret: String = ts_doc_args.into_iter().collect(); - - // only if there is return description as we dont want empty @return tag + // only if there is return description, as we dont want empty @return tag if let Some(ret_desc) = ret_desc { - ret.push_str("@returns "); - ret.push_str(ret_desc); + ts_doc.push_str("@returns "); + ts_doc.push_str(ret_desc); } - ret + ts_doc } } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 005ea750a9a..6f51f4cf541 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2904,9 +2904,9 @@ __wbg_set_wasm(wasm);" let ts_sig = export.generate_typescript.then_some(ts_sig.as_str()); - // only include ts_doc for format if there was arguments or return var description - // this is because if there are no arguments or return var description, ts_doc - // provides no additional info on top of what ts_sig already does + // only include `ts_doc` for format if there were arguments or a return var description + // this is because if there are no arguments or return var description, `ts_doc` + // provides no additional value on top of what `ts_sig` already does let ts_doc_opts = (ret_desc.is_some() || args .as_ref() diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 37f1ec4c31c..a514e875b3d 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -521,20 +521,32 @@ impl<'a> Context<'a> { None => AuxExportKind::Function(export.function.name.to_string()), }; + let args = Some( + export + .function + .args + .into_iter() + .map(|v| AuxFunctionArgumentData { + name: v.name, + ty_override: v.ty_override.map(String::from), + desc: v.desc.map(String::from), + }) + .collect::>(), + ); let id = self.export_adapter(export_id, descriptor)?; self.aux.export_map.insert( id, AuxExport { debug_name: wasm_name, comments: concatenate_comments(&export.comments), - args: Some(export.function.args), + args, asyncness: export.function.asyncness, kind, generate_typescript: export.function.generate_typescript, generate_jsdoc: export.function.generate_jsdoc, variadic: export.function.variadic, - fn_ret_ty_override: export.function.ret_ty_override, - fn_ret_desc: export.function.ret_desc, + fn_ret_ty_override: export.function.ret_ty_override.map(String::from), + fn_ret_desc: export.function.ret_desc.map(String::from), }, ); Ok(()) diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index bfb936ddb29..58de640ed64 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -1,4 +1,3 @@ -use crate::decode::FunctionArgumentData; use crate::intrinsic::Intrinsic; use crate::wit::AdapterId; use std::borrow::Cow; @@ -74,9 +73,9 @@ pub struct AuxExport { pub debug_name: String, /// Comments parsed in Rust and forwarded here to show up in JS bindings. pub comments: String, - /// Argument names in Rust forwarded here to configure the names that show - /// up in TypeScript bindings. - pub args: Option>, + /// Function's argument info in Rust forwarded here to configure the signature + /// that show up in bindings. + pub args: Option>, /// Whether this is an async function, to configure the TypeScript return value. pub asyncness: bool, /// What kind of function this is and where it shows up @@ -93,6 +92,17 @@ pub struct AuxExport { pub fn_ret_desc: Option, } +/// Information about a functions' argument +#[derive(Debug, Clone)] +pub struct AuxFunctionArgumentData { + /// Specifies the argument name + pub name: String, + /// Specifies the function argument type override + pub ty_override: Option, + /// Specifies the argument description + pub desc: Option, +} + /// All possible kinds of exports from a Wasm module. /// /// This `enum` says where to place an exported Wasm function. For example it diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 250ef93dd21..8b94b06d046 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -969,12 +969,12 @@ impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option>)> for syn::ItemFn { +impl ConvertToAst<(BindgenAttrs, Vec)> for syn::ItemFn { type Target = ast::Function; fn convert( self, - (attrs, args_attrs): (BindgenAttrs, Option>), + (attrs, args_attrs): (BindgenAttrs, Vec), ) -> Result { match self.vis { syn::Visibility::Public(_) => {} @@ -995,7 +995,7 @@ impl ConvertToAst<(BindgenAttrs, Option>)> for syn::ItemFn { self.attrs, self.vis, FunctionPosition::Free, - args_attrs, + Some(args_attrs), )?; attrs.check_used(); @@ -1146,7 +1146,7 @@ fn function_from_decl( syn::ReturnType::Default => None, syn::ReturnType::Type(_, ty) => Some(ast::FunctionReturnData { r#type: replace_self(*ty), - ty_override: ty_override.as_ref().map_or(Ok(None), |(ty, span)| { + js_type: ty_override.as_ref().map_or(Ok(None), |(ty, span)| { if is_js_keyword(ty) { return Err(Diagnostic::span_error(*span, "collides with js/ts keyword")); } @@ -1219,7 +1219,7 @@ fn function_from_decl( pat_type: arg_pat_type, js_name: arg_attrs.name, desc: arg_attrs.desc, - ty_override: arg_attrs.ty, + js_type: arg_attrs.ty, }) .collect::>(), }, @@ -1364,7 +1364,7 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { program.exports.push(ast::Export { comments, - function: f.convert((opts, Some(args_attrs)))?, + function: f.convert((opts, args_attrs))?, js_class: None, method_kind, method_self: None, diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 50dab6b206a..3e6db120664 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -135,20 +135,20 @@ macro_rules! shared_api { } struct Function<'a> { - args: Vec, + args: Vec>, asyncness: bool, name: &'a str, generate_typescript: bool, generate_jsdoc: bool, variadic: bool, - ret_ty_override: Option, - ret_desc: Option, + ret_ty_override: Option<&'a str>, + ret_desc: Option<&'a str>, } - struct FunctionArgumentData { + struct FunctionArgumentData<'a> { name: String, - ty_override: Option, - desc: Option, + ty_override: Option<&'a str>, + desc: Option<&'a str>, } struct Struct<'a> { diff --git a/guide/src/reference/attributes/on-rust-exports/function-attributes.md b/guide/src/reference/attributes/on-rust-exports/function-attributes.md index 0225cc7586c..b7023d28457 100644 --- a/guide/src/reference/attributes/on-rust-exports/function-attributes.md +++ b/guide/src/reference/attributes/on-rust-exports/function-attributes.md @@ -1,6 +1,6 @@ # `function-attributes` -By default, exported Rust functions and methods generate function signature from equivalent Rust types identifiers without any arguments and return variable documentations. However by using function attributes, it's possible to override a function's return type and arguments names and types for generated bindings, as well as the ability to write specific documentation for each of them individually as desired: +By default, exported Rust functions and methods generate function signature from equivalent Rust types identifiers without any arguments and return variable documentations. However by using function attributes, it's possible to override a function's return type and argument names and types for generated bindings, as well as the ability to write specific documentation for each of them individually as desired: - `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(return_description)]` used to override a function's return type and to specify descriptions for generated JS/TS bindings. - `#[wasm_bindgen(js_name)]`, `#[wasm_bindgen(unchecked_param_type)]` and `#[wasm_bindgen(param_description)]` applied to a Rust function argument to override that argument's name and type and to specify descriptions for generated JS/TS bindings. From 76baf36183fc93e345c7a10f8251374a6d476fc6 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 9 Jan 2025 22:09:18 +0000 Subject: [PATCH 35/50] update - ident validation for arg attr js_name - move ident validation fn to shared crate - update tests --- crates/backend/src/encode.rs | 3 +- crates/cli-support/Cargo.toml | 1 - crates/cli-support/src/descriptor.rs | 2 +- crates/cli-support/src/js/mod.rs | 3 +- crates/macro-support/src/parser.rs | 43 ++-- crates/macro/ui-tests/dup-fn-attrs.rs | 31 --- crates/macro/ui-tests/dup-fn-attrs.stderr | 17 -- .../macro/ui-tests/illegal-char-fn-attrs.rs | 2 +- .../ui-tests/illegal-char-fn-attrs.stderr | 6 +- crates/macro/ui-tests/invalid-fn-arg-name.rs | 93 +++++++ .../macro/ui-tests/invalid-fn-arg-name.stderr | 71 ++++++ crates/macro/ui-tests/no-ret-fn-attr.stderr | 8 +- crates/macro/ui-tests/unused-attributes.rs | 40 --- .../macro/ui-tests/unused-attributes.stderr | 208 --------------- crates/macro/ui-tests/unused-fn-attrs.rs | 85 +++++++ crates/macro/ui-tests/unused-fn-attrs.stderr | 237 ++++++++++++++++++ crates/shared/Cargo.toml | 3 + .../src/js => shared/src}/identifier.rs | 0 crates/shared/src/lib.rs | 1 + crates/shared/src/schema_hash_approval.rs | 2 +- 20 files changed, 526 insertions(+), 330 deletions(-) delete mode 100644 crates/macro/ui-tests/dup-fn-attrs.rs delete mode 100644 crates/macro/ui-tests/dup-fn-attrs.stderr create mode 100644 crates/macro/ui-tests/invalid-fn-arg-name.rs create mode 100644 crates/macro/ui-tests/invalid-fn-arg-name.stderr create mode 100644 crates/macro/ui-tests/unused-fn-attrs.rs create mode 100644 crates/macro/ui-tests/unused-fn-attrs.stderr rename crates/{cli-support/src/js => shared/src}/identifier.rs (100%) diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index 8d55306b5ba..1285f19fc93 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -220,7 +220,8 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi .iter() .enumerate() .map(|(idx, arg)| FunctionArgumentData { - // use argument's "js_name" if it was provided in attributes + // use argument's "js_name" if it was provided via attributes + // if not use the original Rust argument ident name: arg.js_name.clone().unwrap_or( if let syn::Pat::Ident(x) = &*arg.pat_type.pat { x.ident.unraw().to_string() diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index 6a4a5d94eee..60e3e6fe6f0 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -21,7 +21,6 @@ rustc-demangle = "0.1.13" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tempfile = "3.0" -unicode-ident = "1.0.5" walrus = { version = "0.23", features = ['parallel'] } wasm-bindgen-externref-xform = { path = '../externref-xform', version = '=0.2.99' } wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.99' } diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index d9b7fd63eab..3ddf75405b3 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -1,6 +1,6 @@ use std::char; -use crate::js::identifier::is_valid_ident; +use wasm_bindgen_shared::identifier::is_valid_ident; macro_rules! tys { ($($a:ident)*) => (tys! { @ ($($a)*) 0 }); diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 6f51f4cf541..3ba5be3a719 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -10,7 +10,6 @@ use crate::wit::{JsImport, JsImportName, NonstandardWitSection, WasmBindgenAux}; use crate::{reset_indentation, Bindgen, EncodeInto, OutputMode, PLACEHOLDER_MODULE}; use anyhow::{anyhow, bail, Context as _, Error}; use binding::TsReference; -use identifier::is_valid_ident; use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fmt; @@ -18,9 +17,9 @@ use std::fmt::Write; use std::fs; use std::path::{Path, PathBuf}; use walrus::{FunctionId, ImportId, MemoryId, Module, TableId, ValType}; +use wasm_bindgen_shared::identifier::is_valid_ident; mod binding; -pub mod identifier; pub struct Context<'a> { globals: String, diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 8b94b06d046..3ec68e72c02 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -9,6 +9,7 @@ use backend::util::{ident_ty, ShortHash}; use backend::Diagnostic; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::ToTokens; +use shared::identifier::is_valid_ident; use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream, Result as SynResult}; use syn::spanned::Spanned; @@ -103,6 +104,11 @@ fn is_non_value_js_keyword(keyword: &str) -> bool { JS_KEYWORDS.contains(&keyword) && !VALUE_LIKE_JS_KEYWORDS.contains(&keyword) } +/// Checks if the given string contains JS/TS comment block close syntax +fn contains_js_comment_close(str: &str) -> bool { + str.contains("*/") +} + #[derive(Default)] struct AttributeParseState { parsed: Cell, @@ -1169,19 +1175,19 @@ fn function_from_decl( })?, }), }; - // error if specified description or type override for return - // while the function doesn't return anything + // error if there were description or type override specified for + // function return while the it doesn't return anything if ret.is_none() && (ty_override.is_some() || desc.is_some()) { if let Some((_, span)) = ty_override { return Err(Diagnostic::span_error( span, - "cannot specify type for a function that doesn't return", + "cannot specify return type for a function that doesn't return", )); } if let Some((_, span)) = desc { return Err(Diagnostic::span_error( span, - "cannot specify description for a function that doesn't return", + "cannot specify return description for a function that doesn't return", )); } } @@ -1215,11 +1221,11 @@ fn function_from_decl( arguments: arguments .into_iter() .zip(args_attrs.unwrap_or(vec![FnArgAttrs::default(); args_len])) - .map(|(arg_pat_type, arg_attrs)| FunctionArgumentData { - pat_type: arg_pat_type, - js_name: arg_attrs.name, - desc: arg_attrs.desc, - js_type: arg_attrs.ty, + .map(|(pat_type, attrs)| FunctionArgumentData { + pat_type, + js_name: attrs.js_name, + js_type: attrs.js_type, + desc: attrs.desc, }) .collect::>(), }, @@ -1227,11 +1233,12 @@ fn function_from_decl( )) } +/// Helper struct to store extracted function argument attrs #[derive(Default, Clone)] struct FnArgAttrs { - ty: Option, + js_name: Option, + js_type: Option, desc: Option, - name: Option, } /// Extracts function arguments attributes @@ -1241,7 +1248,7 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn if let syn::FnArg::Typed(pat_type) = input { let attrs = BindgenAttrs::find(&mut pat_type.attrs)?; let arg_attrs = FnArgAttrs { - name: attrs + js_name: attrs .js_name() .map_or(Ok(None), |(js_name_override, span)| { if is_js_keyword(js_name_override) { @@ -1250,15 +1257,15 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn "collides with js/ts keyword", )); } - if contains_js_comment_close(js_name_override) { + if !is_valid_ident(js_name_override) { return Err(Diagnostic::span_error( span, - "contains illegal comment close syntax", + "invalid js/ts argument identifier", )); } Ok(Some(js_name_override.to_string())) })?, - ty: attrs + js_type: attrs .unchecked_param_type() .map_or(Ok(None), |(ty, span)| { if is_js_keyword(ty) { @@ -1287,6 +1294,7 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn Ok(Some(description.to_string())) })?, }; + // throw error for any unused attrs attrs.enforce_used()?; args_attrs.push(arg_attrs); } @@ -1294,11 +1302,6 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn Ok(args_attrs) } -/// Checks if the given string contains JS/TS comment block close syntax -fn contains_js_comment_close(str: &str) -> bool { - str.contains("*/") -} - pub(crate) trait MacroParse { /// Parse the contents of an object into our AST, with a context if necessary. /// diff --git a/crates/macro/ui-tests/dup-fn-attrs.rs b/crates/macro/ui-tests/dup-fn-attrs.rs deleted file mode 100644 index c52b20b0f36..00000000000 --- a/crates/macro/ui-tests/dup-fn-attrs.rs +++ /dev/null @@ -1,31 +0,0 @@ -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -pub fn fn_with_dup_attr1( - #[wasm_bindgen(js_name = "firstArg", js_name = "anotherFirstArg")] arg1: u32, -) -> JsValue { - arg1.into() -} - -#[wasm_bindgen] -pub async fn fn_with_dup_attr2( - #[wasm_bindgen(unchecked_param_type = "number", unchecked_param_type = "bigint")] arg1: u32, -) -> JsValue { - arg1.into() -} - -#[wasm_bindgen] -pub struct A {} - -#[wasm_bindgen] -impl A { - #[wasm_bindgen] - pub async fn fn_with_dup_attr3( - #[wasm_bindgen(param_description = "some number")] - #[wasm_bindgen(param_description = "some other description")] arg1: u32, - ) -> JsValue { - arg1.into() - } -} - -fn main() {} diff --git a/crates/macro/ui-tests/dup-fn-attrs.stderr b/crates/macro/ui-tests/dup-fn-attrs.stderr deleted file mode 100644 index 09658cd09a0..00000000000 --- a/crates/macro/ui-tests/dup-fn-attrs.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error: unused wasm_bindgen attribute - --> ui-tests/dup-fn-attrs.rs:5:42 - | -5 | #[wasm_bindgen(js_name = "firstArg", js_name = "anotherFirstArg")] arg1: u32, - | ^^^^^^^ - -error: unused wasm_bindgen attribute - --> ui-tests/dup-fn-attrs.rs:12:53 - | -12 | #[wasm_bindgen(unchecked_param_type = "number", unchecked_param_type = "bigint")] arg1: u32, - | ^^^^^^^^^^^^^^^^^^^^ - -error: unused wasm_bindgen attribute - --> ui-tests/dup-fn-attrs.rs:25:24 - | -25 | #[wasm_bindgen(param_description = "some other description")] arg1: u32, - | ^^^^^^^^^^^^^^^^^ diff --git a/crates/macro/ui-tests/illegal-char-fn-attrs.rs b/crates/macro/ui-tests/illegal-char-fn-attrs.rs index d7213c56fa7..11ba1fa8cda 100644 --- a/crates/macro/ui-tests/illegal-char-fn-attrs.rs +++ b/crates/macro/ui-tests/illegal-char-fn-attrs.rs @@ -2,7 +2,7 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn fn_with_illegal_char_attr1( - #[wasm_bindgen(js_name = "abcd */firstArg")] arg1: u32, + #[wasm_bindgen(unchecked_param_type = "abcd */firstArg")] arg1: u32, ) -> JsValue { arg1.into() } diff --git a/crates/macro/ui-tests/illegal-char-fn-attrs.stderr b/crates/macro/ui-tests/illegal-char-fn-attrs.stderr index 3d90faba3d8..d03f1e56c73 100644 --- a/crates/macro/ui-tests/illegal-char-fn-attrs.stderr +++ b/crates/macro/ui-tests/illegal-char-fn-attrs.stderr @@ -1,8 +1,8 @@ error: contains illegal comment close syntax - --> ui-tests/illegal-char-fn-attrs.rs:5:30 + --> ui-tests/illegal-char-fn-attrs.rs:5:43 | -5 | #[wasm_bindgen(js_name = "abcd */firstArg")] arg1: u32, - | ^^^^^^^^^^^^^^^^^ +5 | #[wasm_bindgen(unchecked_param_type = "abcd */firstArg")] arg1: u32, + | ^^^^^^^^^^^^^^^^^ error: contains illegal comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:12:43 diff --git a/crates/macro/ui-tests/invalid-fn-arg-name.rs b/crates/macro/ui-tests/invalid-fn-arg-name.rs new file mode 100644 index 00000000000..d6e882db168 --- /dev/null +++ b/crates/macro/ui-tests/invalid-fn-arg-name.rs @@ -0,0 +1,93 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn fn_with_invalid_arg_name1( + #[wasm_bindgen(js_name = "*firstArg")] arg: u32, +) -> JsValue { + arg.into() +} + +#[wasm_bindgen] +pub fn fn_with_invalid_arg_name2( + #[wasm_bindgen(js_name = "#firstArg")] arg: u32, +) -> JsValue { + arg.into() +} + +#[wasm_bindgen] +pub fn fn_with_invalid_arg_name3( + #[wasm_bindgen(js_name = "firstArg#")] arg: u32, +) -> JsValue { + arg.into() +} + +#[wasm_bindgen] +pub fn fn_with_invalid_arg_name4( + #[wasm_bindgen(js_name = "first-Arg")] arg: u32, +) -> JsValue { + arg.into() +} + +#[wasm_bindgen] +pub fn fn_with_invalid_arg_name5( + #[wasm_bindgen(js_name = "--firstArg")] arg: u32, +) -> JsValue { + arg.into() +} + +#[wasm_bindgen] +pub fn fn_with_invalid_arg_name6( + #[wasm_bindgen(js_name = " first Arg")] arg: u32, +) -> JsValue { + arg.into() +} + +#[wasm_bindgen] +pub struct A {} + +#[wasm_bindgen] +impl A { + #[wasm_bindgen] + pub async fn method_with_invalid_arg_name1( + #[wasm_bindgen(js_name = "(firstArg)")] arg: u32, + ) -> JsValue { + arg.into() + } + + #[wasm_bindgen] + pub async fn method_with_invalid_arg_name2( + #[wasm_bindgen(js_name = "[firstArg]")] arg: u32, + ) -> JsValue { + arg.into() + } + + #[wasm_bindgen] + pub async fn method_with_invalid_arg_name3( + #[wasm_bindgen(js_name = "")] arg: u32, + ) -> JsValue { + arg.into() + } + + #[wasm_bindgen] + pub async fn method_with_invalid_arg_name4( + #[wasm_bindgen(js_name = "firstArg+")] arg: u32, + ) -> JsValue { + arg.into() + } + + #[wasm_bindgen] + pub async fn method_with_invalid_arg_name5( + #[wasm_bindgen(js_name = "@firstArg")] arg: u32, + ) -> JsValue { + arg.into() + } + + #[wasm_bindgen] + pub async fn method_with_invalid_arg_name6( + #[wasm_bindgen(js_name = "!firstArg")] arg: u32, + ) -> JsValue { + arg.into() + } +} + +fn main() {} diff --git a/crates/macro/ui-tests/invalid-fn-arg-name.stderr b/crates/macro/ui-tests/invalid-fn-arg-name.stderr new file mode 100644 index 00000000000..315e6bbdba2 --- /dev/null +++ b/crates/macro/ui-tests/invalid-fn-arg-name.stderr @@ -0,0 +1,71 @@ +error: invalid js/ts argument identifier + --> ui-tests/invalid-fn-arg-name.rs:5:30 + | +5 | #[wasm_bindgen(js_name = "*firstArg")] arg: u32, + | ^^^^^^^^^^^ + +error: invalid js/ts argument identifier + --> ui-tests/invalid-fn-arg-name.rs:12:30 + | +12 | #[wasm_bindgen(js_name = "#firstArg")] arg: u32, + | ^^^^^^^^^^^ + +error: invalid js/ts argument identifier + --> ui-tests/invalid-fn-arg-name.rs:19:30 + | +19 | #[wasm_bindgen(js_name = "firstArg#")] arg: u32, + | ^^^^^^^^^^^ + +error: invalid js/ts argument identifier + --> ui-tests/invalid-fn-arg-name.rs:26:30 + | +26 | #[wasm_bindgen(js_name = "first-Arg")] arg: u32, + | ^^^^^^^^^^^ + +error: invalid js/ts argument identifier + --> ui-tests/invalid-fn-arg-name.rs:33:30 + | +33 | #[wasm_bindgen(js_name = "--firstArg")] arg: u32, + | ^^^^^^^^^^^^ + +error: invalid js/ts argument identifier + --> ui-tests/invalid-fn-arg-name.rs:40:30 + | +40 | #[wasm_bindgen(js_name = " first Arg")] arg: u32, + | ^^^^^^^^^^^^ + +error: invalid js/ts argument identifier + --> ui-tests/invalid-fn-arg-name.rs:52:34 + | +52 | #[wasm_bindgen(js_name = "(firstArg)")] arg: u32, + | ^^^^^^^^^^^^ + +error: invalid js/ts argument identifier + --> ui-tests/invalid-fn-arg-name.rs:59:34 + | +59 | #[wasm_bindgen(js_name = "[firstArg]")] arg: u32, + | ^^^^^^^^^^^^ + +error: invalid js/ts argument identifier + --> ui-tests/invalid-fn-arg-name.rs:66:34 + | +66 | #[wasm_bindgen(js_name = "")] arg: u32, + | ^^^^^^^^^^^^ + +error: invalid js/ts argument identifier + --> ui-tests/invalid-fn-arg-name.rs:73:34 + | +73 | #[wasm_bindgen(js_name = "firstArg+")] arg: u32, + | ^^^^^^^^^^^ + +error: invalid js/ts argument identifier + --> ui-tests/invalid-fn-arg-name.rs:80:34 + | +80 | #[wasm_bindgen(js_name = "@firstArg")] arg: u32, + | ^^^^^^^^^^^ + +error: invalid js/ts argument identifier + --> ui-tests/invalid-fn-arg-name.rs:87:34 + | +87 | #[wasm_bindgen(js_name = "!firstArg")] arg: u32, + | ^^^^^^^^^^^ diff --git a/crates/macro/ui-tests/no-ret-fn-attr.stderr b/crates/macro/ui-tests/no-ret-fn-attr.stderr index 74192856681..66a6bdda801 100644 --- a/crates/macro/ui-tests/no-ret-fn-attr.stderr +++ b/crates/macro/ui-tests/no-ret-fn-attr.stderr @@ -1,22 +1,22 @@ -error: cannot specify type for a function that doesn't return +error: cannot specify return type for a function that doesn't return --> ui-tests/no-ret-fn-attr.rs:3:40 | 3 | #[wasm_bindgen(unchecked_return_type = "number")] | ^^^^^^^^ -error: cannot specify description for a function that doesn't return +error: cannot specify return description for a function that doesn't return --> ui-tests/no-ret-fn-attr.rs:6:37 | 6 | #[wasm_bindgen(return_description = "some description")] | ^^^^^^^^^^^^^^^^^^ -error: cannot specify type for a function that doesn't return +error: cannot specify return type for a function that doesn't return --> ui-tests/no-ret-fn-attr.rs:14:44 | 14 | #[wasm_bindgen(unchecked_return_type = "number")] | ^^^^^^^^ -error: cannot specify description for a function that doesn't return +error: cannot specify return description for a function that doesn't return --> ui-tests/no-ret-fn-attr.rs:17:41 | 17 | #[wasm_bindgen(return_description = "some description")] diff --git a/crates/macro/ui-tests/unused-attributes.rs b/crates/macro/ui-tests/unused-attributes.rs index 030254d8087..f517dcc4b55 100644 --- a/crates/macro/ui-tests/unused-attributes.rs +++ b/crates/macro/ui-tests/unused-attributes.rs @@ -29,44 +29,4 @@ impl MyStruct { } } -#[wasm_bindgen( - unchecked_return_type = "something", - return_description = "something", - unchecked_param_type = "something", - param_description = "somthing" -)] -struct B {} - -#[wasm_bindgen( - unchecked_return_type = "something", - return_description = "something", - unchecked_param_type = "something", - param_description = "somthing" -)] -impl B { - #[wasm_bindgen] - pub fn foo() {} -} - -#[wasm_bindgen( - unchecked_return_type = "something", - return_description = "something", - unchecked_param_type = "something", - param_description = "somthing" -)] -pub enum D { - Variat -} - -#[wasm_bindgen( - unchecked_return_type = "something", - return_description = "something", - unchecked_param_type = "something", - param_description = "somthing" -)] -impl D { - #[wasm_bindgen] - pub fn foo() {} -} - fn main() {} diff --git a/crates/macro/ui-tests/unused-attributes.stderr b/crates/macro/ui-tests/unused-attributes.stderr index 26dc2accd3f..1df8f26a021 100644 --- a/crates/macro/ui-tests/unused-attributes.stderr +++ b/crates/macro/ui-tests/unused-attributes.stderr @@ -1,211 +1,3 @@ -error: invalid attribute unchecked_return_type in this position - --> ui-tests/unused-attributes.rs:32:1 - | -32 | / #[wasm_bindgen( -33 | | unchecked_return_type = "something", -34 | | return_description = "something", -35 | | unchecked_param_type = "something", -36 | | param_description = "somthing" -37 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute return_description in this position - --> ui-tests/unused-attributes.rs:32:1 - | -32 | / #[wasm_bindgen( -33 | | unchecked_return_type = "something", -34 | | return_description = "something", -35 | | unchecked_param_type = "something", -36 | | param_description = "somthing" -37 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute unchecked_param_type in this position - --> ui-tests/unused-attributes.rs:32:1 - | -32 | / #[wasm_bindgen( -33 | | unchecked_return_type = "something", -34 | | return_description = "something", -35 | | unchecked_param_type = "something", -36 | | param_description = "somthing" -37 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute param_description in this position - --> ui-tests/unused-attributes.rs:32:1 - | -32 | / #[wasm_bindgen( -33 | | unchecked_return_type = "something", -34 | | return_description = "something", -35 | | unchecked_param_type = "something", -36 | | param_description = "somthing" -37 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute unchecked_return_type in this position - --> ui-tests/unused-attributes.rs:40:1 - | -40 | / #[wasm_bindgen( -41 | | unchecked_return_type = "something", -42 | | return_description = "something", -43 | | unchecked_param_type = "something", -44 | | param_description = "somthing" -45 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute return_description in this position - --> ui-tests/unused-attributes.rs:40:1 - | -40 | / #[wasm_bindgen( -41 | | unchecked_return_type = "something", -42 | | return_description = "something", -43 | | unchecked_param_type = "something", -44 | | param_description = "somthing" -45 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute unchecked_param_type in this position - --> ui-tests/unused-attributes.rs:40:1 - | -40 | / #[wasm_bindgen( -41 | | unchecked_return_type = "something", -42 | | return_description = "something", -43 | | unchecked_param_type = "something", -44 | | param_description = "somthing" -45 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute param_description in this position - --> ui-tests/unused-attributes.rs:40:1 - | -40 | / #[wasm_bindgen( -41 | | unchecked_return_type = "something", -42 | | return_description = "something", -43 | | unchecked_param_type = "something", -44 | | param_description = "somthing" -45 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute unchecked_return_type in this position - --> ui-tests/unused-attributes.rs:51:1 - | -51 | / #[wasm_bindgen( -52 | | unchecked_return_type = "something", -53 | | return_description = "something", -54 | | unchecked_param_type = "something", -55 | | param_description = "somthing" -56 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute return_description in this position - --> ui-tests/unused-attributes.rs:51:1 - | -51 | / #[wasm_bindgen( -52 | | unchecked_return_type = "something", -53 | | return_description = "something", -54 | | unchecked_param_type = "something", -55 | | param_description = "somthing" -56 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute unchecked_param_type in this position - --> ui-tests/unused-attributes.rs:51:1 - | -51 | / #[wasm_bindgen( -52 | | unchecked_return_type = "something", -53 | | return_description = "something", -54 | | unchecked_param_type = "something", -55 | | param_description = "somthing" -56 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute param_description in this position - --> ui-tests/unused-attributes.rs:51:1 - | -51 | / #[wasm_bindgen( -52 | | unchecked_return_type = "something", -53 | | return_description = "something", -54 | | unchecked_param_type = "something", -55 | | param_description = "somthing" -56 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute unchecked_return_type in this position - --> ui-tests/unused-attributes.rs:61:1 - | -61 | / #[wasm_bindgen( -62 | | unchecked_return_type = "something", -63 | | return_description = "something", -64 | | unchecked_param_type = "something", -65 | | param_description = "somthing" -66 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute return_description in this position - --> ui-tests/unused-attributes.rs:61:1 - | -61 | / #[wasm_bindgen( -62 | | unchecked_return_type = "something", -63 | | return_description = "something", -64 | | unchecked_param_type = "something", -65 | | param_description = "somthing" -66 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute unchecked_param_type in this position - --> ui-tests/unused-attributes.rs:61:1 - | -61 | / #[wasm_bindgen( -62 | | unchecked_return_type = "something", -63 | | return_description = "something", -64 | | unchecked_param_type = "something", -65 | | param_description = "somthing" -66 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: invalid attribute param_description in this position - --> ui-tests/unused-attributes.rs:61:1 - | -61 | / #[wasm_bindgen( -62 | | unchecked_return_type = "something", -63 | | return_description = "something", -64 | | unchecked_param_type = "something", -65 | | param_description = "somthing" -66 | | )] - | |__^ - | - = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) - error: unused variable: `method` --> ui-tests/unused-attributes.rs:9:20 | diff --git a/crates/macro/ui-tests/unused-fn-attrs.rs b/crates/macro/ui-tests/unused-fn-attrs.rs new file mode 100644 index 00000000000..d483273a5cd --- /dev/null +++ b/crates/macro/ui-tests/unused-fn-attrs.rs @@ -0,0 +1,85 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn fn_with_unused_attr1( + #[wasm_bindgen(js_name = "firstArg", js_name = "anotherFirstArg")] arg: u32, +) -> JsValue { + arg.into() +} + +#[wasm_bindgen] +pub async fn fn_with_unused_attr2( + #[wasm_bindgen(unchecked_param_type = "number", unchecked_param_type = "bigint")] arg: u32, +) -> JsValue { + arg.into() +} + +#[wasm_bindgen] +pub async fn fn_with_unused_attr3( + #[wasm_bindgen(unchecked_return_type = "number")] arg: u32, +) -> JsValue { + arg.into() +} + +#[wasm_bindgen] +pub async fn fn_with_unused_attr4( + #[wasm_bindgen(return_description = "some description")] arg: u32, +) -> JsValue { + arg.into() +} + +#[wasm_bindgen] +pub struct A {} + +#[wasm_bindgen] +impl A { + #[wasm_bindgen] + pub async fn fn_with_unused_attr( + #[wasm_bindgen(param_description = "some number")] + #[wasm_bindgen(param_description = "some other description")] arg: u32, + ) -> JsValue { + arg.into() + } +} + +#[wasm_bindgen( + unchecked_return_type = "something", + return_description = "something", + unchecked_param_type = "something", + param_description = "somthing" +)] +struct B {} + +#[wasm_bindgen( + unchecked_return_type = "something", + return_description = "something", + unchecked_param_type = "something", + param_description = "somthing" +)] +impl B { + #[wasm_bindgen] + pub fn foo() {} +} + +#[wasm_bindgen( + unchecked_return_type = "something", + return_description = "something", + unchecked_param_type = "something", + param_description = "somthing" +)] +pub enum C { + Variat +} + +#[wasm_bindgen( + unchecked_return_type = "something", + return_description = "something", + unchecked_param_type = "something", + param_description = "somthing" +)] +impl C { + #[wasm_bindgen] + pub fn foo() {} +} + +fn main() {} diff --git a/crates/macro/ui-tests/unused-fn-attrs.stderr b/crates/macro/ui-tests/unused-fn-attrs.stderr new file mode 100644 index 00000000000..1d53ef47e89 --- /dev/null +++ b/crates/macro/ui-tests/unused-fn-attrs.stderr @@ -0,0 +1,237 @@ +error: unused wasm_bindgen attribute + --> ui-tests/unused-fn-attrs.rs:5:42 + | +5 | #[wasm_bindgen(js_name = "firstArg", js_name = "anotherFirstArg")] arg: u32, + | ^^^^^^^ + +error: unused wasm_bindgen attribute + --> ui-tests/unused-fn-attrs.rs:12:53 + | +12 | #[wasm_bindgen(unchecked_param_type = "number", unchecked_param_type = "bigint")] arg: u32, + | ^^^^^^^^^^^^^^^^^^^^ + +error: unused wasm_bindgen attribute + --> ui-tests/unused-fn-attrs.rs:19:20 + | +19 | #[wasm_bindgen(unchecked_return_type = "number")] arg: u32, + | ^^^^^^^^^^^^^^^^^^^^^ + +error: unused wasm_bindgen attribute + --> ui-tests/unused-fn-attrs.rs:26:20 + | +26 | #[wasm_bindgen(return_description = "some description")] arg: u32, + | ^^^^^^^^^^^^^^^^^^ + +error: unused wasm_bindgen attribute + --> ui-tests/unused-fn-attrs.rs:39:24 + | +39 | #[wasm_bindgen(param_description = "some other description")] arg: u32, + | ^^^^^^^^^^^^^^^^^ + +error: invalid attribute unchecked_return_type in this position + --> ui-tests/unused-fn-attrs.rs:45:1 + | +45 | / #[wasm_bindgen( +46 | | unchecked_return_type = "something", +47 | | return_description = "something", +48 | | unchecked_param_type = "something", +49 | | param_description = "somthing" +50 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute return_description in this position + --> ui-tests/unused-fn-attrs.rs:45:1 + | +45 | / #[wasm_bindgen( +46 | | unchecked_return_type = "something", +47 | | return_description = "something", +48 | | unchecked_param_type = "something", +49 | | param_description = "somthing" +50 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_param_type in this position + --> ui-tests/unused-fn-attrs.rs:45:1 + | +45 | / #[wasm_bindgen( +46 | | unchecked_return_type = "something", +47 | | return_description = "something", +48 | | unchecked_param_type = "something", +49 | | param_description = "somthing" +50 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute param_description in this position + --> ui-tests/unused-fn-attrs.rs:45:1 + | +45 | / #[wasm_bindgen( +46 | | unchecked_return_type = "something", +47 | | return_description = "something", +48 | | unchecked_param_type = "something", +49 | | param_description = "somthing" +50 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_return_type in this position + --> ui-tests/unused-fn-attrs.rs:53:1 + | +53 | / #[wasm_bindgen( +54 | | unchecked_return_type = "something", +55 | | return_description = "something", +56 | | unchecked_param_type = "something", +57 | | param_description = "somthing" +58 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute return_description in this position + --> ui-tests/unused-fn-attrs.rs:53:1 + | +53 | / #[wasm_bindgen( +54 | | unchecked_return_type = "something", +55 | | return_description = "something", +56 | | unchecked_param_type = "something", +57 | | param_description = "somthing" +58 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_param_type in this position + --> ui-tests/unused-fn-attrs.rs:53:1 + | +53 | / #[wasm_bindgen( +54 | | unchecked_return_type = "something", +55 | | return_description = "something", +56 | | unchecked_param_type = "something", +57 | | param_description = "somthing" +58 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute param_description in this position + --> ui-tests/unused-fn-attrs.rs:53:1 + | +53 | / #[wasm_bindgen( +54 | | unchecked_return_type = "something", +55 | | return_description = "something", +56 | | unchecked_param_type = "something", +57 | | param_description = "somthing" +58 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_return_type in this position + --> ui-tests/unused-fn-attrs.rs:64:1 + | +64 | / #[wasm_bindgen( +65 | | unchecked_return_type = "something", +66 | | return_description = "something", +67 | | unchecked_param_type = "something", +68 | | param_description = "somthing" +69 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute return_description in this position + --> ui-tests/unused-fn-attrs.rs:64:1 + | +64 | / #[wasm_bindgen( +65 | | unchecked_return_type = "something", +66 | | return_description = "something", +67 | | unchecked_param_type = "something", +68 | | param_description = "somthing" +69 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_param_type in this position + --> ui-tests/unused-fn-attrs.rs:64:1 + | +64 | / #[wasm_bindgen( +65 | | unchecked_return_type = "something", +66 | | return_description = "something", +67 | | unchecked_param_type = "something", +68 | | param_description = "somthing" +69 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute param_description in this position + --> ui-tests/unused-fn-attrs.rs:64:1 + | +64 | / #[wasm_bindgen( +65 | | unchecked_return_type = "something", +66 | | return_description = "something", +67 | | unchecked_param_type = "something", +68 | | param_description = "somthing" +69 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_return_type in this position + --> ui-tests/unused-fn-attrs.rs:74:1 + | +74 | / #[wasm_bindgen( +75 | | unchecked_return_type = "something", +76 | | return_description = "something", +77 | | unchecked_param_type = "something", +78 | | param_description = "somthing" +79 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute return_description in this position + --> ui-tests/unused-fn-attrs.rs:74:1 + | +74 | / #[wasm_bindgen( +75 | | unchecked_return_type = "something", +76 | | return_description = "something", +77 | | unchecked_param_type = "something", +78 | | param_description = "somthing" +79 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute unchecked_param_type in this position + --> ui-tests/unused-fn-attrs.rs:74:1 + | +74 | / #[wasm_bindgen( +75 | | unchecked_return_type = "something", +76 | | return_description = "something", +77 | | unchecked_param_type = "something", +78 | | param_description = "somthing" +79 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid attribute param_description in this position + --> ui-tests/unused-fn-attrs.rs:74:1 + | +74 | / #[wasm_bindgen( +75 | | unchecked_return_type = "something", +76 | | return_description = "something", +77 | | unchecked_param_type = "something", +78 | | param_description = "somthing" +79 | | )] + | |__^ + | + = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index d69ef902539..be3071b0f38 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -21,3 +21,6 @@ links = "wasm_bindgen" [lints] workspace = true + +[dependencies] +unicode-ident = "1.0.5" diff --git a/crates/cli-support/src/js/identifier.rs b/crates/shared/src/identifier.rs similarity index 100% rename from crates/cli-support/src/js/identifier.rs rename to crates/shared/src/identifier.rs diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 3e6db120664..827d6d51033 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -1,5 +1,6 @@ #![doc(html_root_url = "https://docs.rs/wasm-bindgen-shared/0.2")] +pub mod identifier; #[cfg(test)] mod schema_hash_approval; diff --git a/crates/shared/src/schema_hash_approval.rs b/crates/shared/src/schema_hash_approval.rs index 36221d8e7d7..0f004f64550 100644 --- a/crates/shared/src/schema_hash_approval.rs +++ b/crates/shared/src/schema_hash_approval.rs @@ -8,7 +8,7 @@ // If the schema in this library has changed then: // 1. Bump the version in `crates/shared/Cargo.toml` // 2. Change the `SCHEMA_VERSION` in this library to this new Cargo.toml version -const APPROVED_SCHEMA_FILE_HASH: &str = "3398053298395574572"; +const APPROVED_SCHEMA_FILE_HASH: &str = "15483723499309648925"; #[test] fn schema_version() { From 14dc57a21d0ed7b190357c2e3c0ef94e7ec458f6 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 9 Jan 2025 22:23:15 +0000 Subject: [PATCH 36/50] typo --- crates/macro-support/src/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 3ec68e72c02..5bdb2131ab4 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -1176,7 +1176,7 @@ fn function_from_decl( }), }; // error if there were description or type override specified for - // function return while the it doesn't return anything + // function return while it doesn't return anything if ret.is_none() && (ty_override.is_some() || desc.is_some()) { if let Some((_, span)) = ty_override { return Err(Diagnostic::span_error( From 225acf531c174822469f54a86cc3a74616754869 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 9 Jan 2025 22:48:41 +0000 Subject: [PATCH 37/50] minor fix --- crates/macro-support/src/parser.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 5bdb2131ab4..6882a19253b 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use std::str::Chars; use ast::OperationKind; -use backend::ast::{self, FunctionArgumentData, ThreadLocal}; +use backend::ast::{self, ThreadLocal}; use backend::util::{ident_ty, ShortHash}; use backend::Diagnostic; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; @@ -1146,13 +1146,14 @@ fn function_from_decl( } } - let ty_override = opts.unchecked_return_type(); - let desc = opts.return_description(); + // process function return data + let ret_ty_override = opts.unchecked_return_type(); + let ret_desc = opts.return_description(); let ret = match output { syn::ReturnType::Default => None, syn::ReturnType::Type(_, ty) => Some(ast::FunctionReturnData { r#type: replace_self(*ty), - js_type: ty_override.as_ref().map_or(Ok(None), |(ty, span)| { + js_type: ret_ty_override.as_ref().map_or(Ok(None), |(ty, span)| { if is_js_keyword(ty) { return Err(Diagnostic::span_error(*span, "collides with js/ts keyword")); } @@ -1164,7 +1165,7 @@ fn function_from_decl( } Ok(Some(ty.to_string())) })?, - desc: desc.as_ref().map_or(Ok(None), |(desc, span)| { + desc: ret_desc.as_ref().map_or(Ok(None), |(desc, span)| { if contains_js_comment_close(desc) { return Err(Diagnostic::span_error( *span, @@ -1176,15 +1177,15 @@ fn function_from_decl( }), }; // error if there were description or type override specified for - // function return while it doesn't return anything - if ret.is_none() && (ty_override.is_some() || desc.is_some()) { - if let Some((_, span)) = ty_override { + // function return while it doesn't actually return anything + if ret.is_none() && (ret_ty_override.is_some() || ret_desc.is_some()) { + if let Some((_, span)) = ret_ty_override { return Err(Diagnostic::span_error( span, "cannot specify return type for a function that doesn't return", )); } - if let Some((_, span)) = desc { + if let Some((_, span)) = ret_desc { return Err(Diagnostic::span_error( span, "cannot specify return description for a function that doesn't return", @@ -1221,7 +1222,7 @@ fn function_from_decl( arguments: arguments .into_iter() .zip(args_attrs.unwrap_or(vec![FnArgAttrs::default(); args_len])) - .map(|(pat_type, attrs)| FunctionArgumentData { + .map(|(pat_type, attrs)| ast::FunctionArgumentData { pat_type, js_name: attrs.js_name, js_type: attrs.js_type, From 44f8a558e9c0e3296e201421501fdce19b15be03 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 9 Jan 2025 23:04:20 +0000 Subject: [PATCH 38/50] add test for no fn attr on 'self' argument --- .../macro/ui-tests/invalid-self-fn-attrs.rs | 29 +++++++++++++++++++ .../ui-tests/invalid-self-fn-attrs.stderr | 11 +++++++ 2 files changed, 40 insertions(+) create mode 100644 crates/macro/ui-tests/invalid-self-fn-attrs.rs create mode 100644 crates/macro/ui-tests/invalid-self-fn-attrs.stderr diff --git a/crates/macro/ui-tests/invalid-self-fn-attrs.rs b/crates/macro/ui-tests/invalid-self-fn-attrs.rs new file mode 100644 index 00000000000..628a9a38b49 --- /dev/null +++ b/crates/macro/ui-tests/invalid-self-fn-attrs.rs @@ -0,0 +1,29 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct A { + inner: u32 +} + +#[wasm_bindgen] +impl A { + #[wasm_bindgen] + pub fn method_with_self_attr1( + #[wasm_bindgen(unchecked_param_type = "number")] + &self, + arg: u32 + ) -> JsValue { + (self.inner + arg).into() + } + + #[wasm_bindgen] + pub fn method_with_self_attr2( + #[wasm_bindgen(param_description = "some description")] + &self, + arg: u32 + ) -> JsValue { + (self.inner + arg).into() + } +} + +fn main() {} diff --git a/crates/macro/ui-tests/invalid-self-fn-attrs.stderr b/crates/macro/ui-tests/invalid-self-fn-attrs.stderr new file mode 100644 index 00000000000..885f1465c67 --- /dev/null +++ b/crates/macro/ui-tests/invalid-self-fn-attrs.stderr @@ -0,0 +1,11 @@ +error: expected non-macro attribute, found attribute macro `wasm_bindgen` + --> ui-tests/invalid-self-fn-attrs.rs:12:11 + | +12 | #[wasm_bindgen(unchecked_param_type = "number")] + | ^^^^^^^^^^^^ not a non-macro attribute + +error: expected non-macro attribute, found attribute macro `wasm_bindgen` + --> ui-tests/invalid-self-fn-attrs.rs:21:11 + | +21 | #[wasm_bindgen(param_description = "some description")] + | ^^^^^^^^^^^^ not a non-macro attribute From 1aec8306b0fb018f830e39ad6122ea291c67e4c2 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Thu, 9 Jan 2025 23:35:01 +0000 Subject: [PATCH 39/50] use fold() --- crates/cli-support/src/js/binding.rs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 66a1fa6500b..dc64636cd8a 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -234,13 +234,16 @@ impl<'a, 'b> Builder<'a, 'b> { code.push('('); if variadic { if let Some((last, non_variadic_args)) = function_args.split_last() { - code.push_str( - &non_variadic_args - .iter() - .map(|v| v.name.as_str()) - .collect::>() - .join(", "), - ); + code.push_str(&non_variadic_args.iter().enumerate().fold( + String::new(), + |acc, (i, v)| { + if i == 0 { + v.name.clone() + } else { + format!("{}, {}", acc, v.name.as_str()) + } + }, + )); if !non_variadic_args.is_empty() { code.push_str(", "); } @@ -250,9 +253,14 @@ impl<'a, 'b> Builder<'a, 'b> { code.push_str( &function_args .iter() - .map(|v| v.name.as_str()) - .collect::>() - .join(", "), + .enumerate() + .fold(String::new(), |acc, (i, v)| { + if i == 0 { + v.name.clone() + } else { + format!("{}, {}", acc, v.name.as_str()) + } + }), ); } code.push_str(") {\n"); From 0cbe5a8480606a58ce413cf2d0590917543fca23 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Fri, 10 Jan 2025 00:00:07 +0000 Subject: [PATCH 40/50] better fold() --- crates/cli-support/src/js/binding.rs | 36 +++++++++++++--------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index dc64636cd8a..6da6f4aea2f 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -234,16 +234,15 @@ impl<'a, 'b> Builder<'a, 'b> { code.push('('); if variadic { if let Some((last, non_variadic_args)) = function_args.split_last() { - code.push_str(&non_variadic_args.iter().enumerate().fold( - String::new(), - |acc, (i, v)| { - if i == 0 { - v.name.clone() - } else { - format!("{}, {}", acc, v.name.as_str()) - } - }, - )); + code.push_str( + &non_variadic_args.iter().skip(1).fold( + non_variadic_args + .first() + .map(|v| v.name.clone()) + .unwrap_or(String::new()), + |acc, v| format!("{}, {}", acc, v.name), + ), + ); if !non_variadic_args.is_empty() { code.push_str(", "); } @@ -251,16 +250,13 @@ impl<'a, 'b> Builder<'a, 'b> { } } else { code.push_str( - &function_args - .iter() - .enumerate() - .fold(String::new(), |acc, (i, v)| { - if i == 0 { - v.name.clone() - } else { - format!("{}, {}", acc, v.name.as_str()) - } - }), + &function_args.iter().skip(1).fold( + function_args + .first() + .map(|v| v.name.clone()) + .unwrap_or(String::new()), + |acc, v| format!("{}, {}", acc, v.name), + ), ); } code.push_str(") {\n"); From c3468e9b3ad15bd62f909c839284d9059e1d2d0b Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Sat, 11 Jan 2025 20:20:45 +0000 Subject: [PATCH 41/50] even better fold() --- crates/cli-support/src/js/binding.rs | 44 +++++++++++----------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 6da6f4aea2f..f0e1f8d7ea0 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -232,33 +232,23 @@ impl<'a, 'b> Builder<'a, 'b> { let mut code = String::new(); code.push('('); - if variadic { - if let Some((last, non_variadic_args)) = function_args.split_last() { - code.push_str( - &non_variadic_args.iter().skip(1).fold( - non_variadic_args - .first() - .map(|v| v.name.clone()) - .unwrap_or(String::new()), - |acc, v| format!("{}, {}", acc, v.name), - ), - ); - if !non_variadic_args.is_empty() { - code.push_str(", "); - } - code.push_str((String::from("...") + &last.name).as_str()) - } - } else { - code.push_str( - &function_args.iter().skip(1).fold( - function_args - .first() - .map(|v| v.name.clone()) - .unwrap_or(String::new()), - |acc, v| format!("{}, {}", acc, v.name), - ), - ); - } + code.push_str( + &function_args + .iter() + .enumerate() + .fold(String::new(), |acc, (i, v)| { + let arg = if variadic && i == function_args.len() - 1 { + &format!("...{}", v.name) + } else { + &v.name + }; + if i == 0 { + arg.to_string() + } else { + format!("{}, {}", acc, arg) + } + }), + ); code.push_str(") {\n"); let call = if !js.finally.is_empty() { From f5f779ceffcd2d1b7d46d9bceff3295c9e03c12a Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sat, 11 Jan 2025 22:20:00 +0100 Subject: [PATCH 42/50] Reduce new allocations to a minimum --- crates/cli-support/src/js/binding.rs | 28 +++++++++++----------------- crates/macro-support/src/parser.rs | 12 ++++++++---- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index f0e1f8d7ea0..09501a67b40 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -232,23 +232,17 @@ impl<'a, 'b> Builder<'a, 'b> { let mut code = String::new(); code.push('('); - code.push_str( - &function_args - .iter() - .enumerate() - .fold(String::new(), |acc, (i, v)| { - let arg = if variadic && i == function_args.len() - 1 { - &format!("...{}", v.name) - } else { - &v.name - }; - if i == 0 { - arg.to_string() - } else { - format!("{}, {}", acc, arg) - } - }), - ); + for (i, v) in function_args.iter().enumerate() { + if i != 0 { + code.push_str(", "); + } + + if variadic && i == function_args.len() - 1 { + code.push_str("..."); + } + + code.push_str(&v.name); + } code.push_str(") {\n"); let call = if !js.finally.is_empty() { diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 6882a19253b..a64cc471e0d 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -1,7 +1,7 @@ use std::cell::{Cell, RefCell}; -use std::char; use std::collections::HashMap; use std::str::Chars; +use std::{char, iter}; use ast::OperationKind; use backend::ast::{self, ThreadLocal}; @@ -1205,7 +1205,6 @@ fn function_from_decl( (decl_name.unraw().to_string(), decl_name.span(), false) }; - let args_len = arguments.len(); Ok(( ast::Function { name_span, @@ -1221,14 +1220,19 @@ fn function_from_decl( ret, arguments: arguments .into_iter() - .zip(args_attrs.unwrap_or(vec![FnArgAttrs::default(); args_len])) + .zip( + args_attrs + .into_iter() + .flatten() + .chain(iter::repeat(FnArgAttrs::default())), + ) .map(|(pat_type, attrs)| ast::FunctionArgumentData { pat_type, js_name: attrs.js_name, js_type: attrs.js_type, desc: attrs.desc, }) - .collect::>(), + .collect(), }, method_self, )) From e7cb0bee239662d86c30bbbe2a03d1deeac26b9c Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sat, 11 Jan 2025 22:20:18 +0100 Subject: [PATCH 43/50] Adjust some wording --- crates/cli-support/src/wit/nonstandard.rs | 2 +- .../reference/attributes/on-rust-exports/function-attributes.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index 58de640ed64..2742ddb3262 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -74,7 +74,7 @@ pub struct AuxExport { /// Comments parsed in Rust and forwarded here to show up in JS bindings. pub comments: String, /// Function's argument info in Rust forwarded here to configure the signature - /// that show up in bindings. + /// that shows up in bindings. pub args: Option>, /// Whether this is an async function, to configure the TypeScript return value. pub asyncness: bool, diff --git a/guide/src/reference/attributes/on-rust-exports/function-attributes.md b/guide/src/reference/attributes/on-rust-exports/function-attributes.md index b7023d28457..e4d435723dc 100644 --- a/guide/src/reference/attributes/on-rust-exports/function-attributes.md +++ b/guide/src/reference/attributes/on-rust-exports/function-attributes.md @@ -5,7 +5,7 @@ By default, exported Rust functions and methods generate function signature from - `#[wasm_bindgen(js_name)]`, `#[wasm_bindgen(unchecked_param_type)]` and `#[wasm_bindgen(param_description)]` applied to a Rust function argument to override that argument's name and type and to specify descriptions for generated JS/TS bindings. > **NOTE**: -> Types that are provided using `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(unchecked_param_type)]` aren't checked as these attributes' identifiers entail, meaning they will end up in the function's signature and docs bindings exactly as they have been specified and there are no checks/validation for them in place, so only because a user uses `#[wasm_bindgen(unchecked_param_type = "number")]` for example, it doesn't necessarily mean it's actually going to be a value of number type, therefore validation and checks between the value and its type should be handled by the user and the responsibility of using them correctly and carefully relies solely on the user. +> Types that are provided using `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(unchecked_param_type)]` aren't checked for their contents. They will end up in function signature and JSDoc exactly as they have been specified. E.g. `#[wasm_bindgen(unchecked_return_type = "number")]` on a function returning `String` will return a `string`, not a `number`, even if the TS signature and JSDoc will say otherwise. Let's look at some exmaples: ```rust From 9f0665228e4bbad6b6b328778c8201dc5ad68339 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Sun, 12 Jan 2025 00:27:24 +0000 Subject: [PATCH 44/50] minor fix and changelog --- CHANGELOG.md | 6 ++ crates/macro-support/src/parser.rs | 92 +++++++++++++++--------------- 2 files changed, 53 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16bd83de8e0..7a6c82a9484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ ### Added +* Added Function Attributes, which are attributes that can be used on an exporting function to specify/override the function's argument name (`js_name`), type (`unchecked_param_type`) and description (`param_description`) as well as return type (`unchecked_return_type`) and description (`return_description`) for its bindings. + [#4394](https://github.com/rustwasm/wasm-bindgen/pull/4394) + * Add a `copy_to_uninit()` method to all `TypedArray`s. It takes `&mut [MaybeUninit]` and returns `&mut [T]`. [#4340](https://github.com/rustwasm/wasm-bindgen/pull/4340) @@ -19,6 +22,9 @@ ### Changed +* Moved `crates/cli-support/src/js/identifier.rs` to `crates/shared/src/identifier.rs`. + [#4394](https://github.com/rustwasm/wasm-bindgen/pull/4394) + * Optional parameters are now typed as `T | undefined | null` to reflect the actual JS behavior. [#4188](https://github.com/rustwasm/wasm-bindgen/pull/4188) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index a64cc471e0d..1397a60964d 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -109,6 +109,48 @@ fn contains_js_comment_close(str: &str) -> bool { str.contains("*/") } +/// Checks if the given string is an invalid JS/TS identifier +fn is_invalid_ident(str: &str) -> bool { + is_js_keyword(str) || !is_valid_ident(str) +} + +/// Immediately fail and return an Err if the given string +/// contains illegal JS/TS comment close syntax ('*/') +macro_rules! bail_if_incl_illegal_char { + ($str:expr, $span:expr) => { + if contains_js_comment_close($str) { + return Err(Diagnostic::span_error( + $span, + "contains illegal comment close syntax", + )); + } + }; +} + +/// Immediately fail and return an Err if the +/// given string is not a valid JS/TS identifier +macro_rules! bail_if_invalid_ident { + ($str:expr, $span:expr) => { + if is_invalid_ident($str) { + return Err(Diagnostic::span_error( + $span, + "invalid js/ts argument identifier", + )); + } + }; +} + +/// Immediately fail and return an Err if the +/// given string is not a valid JS/TS type +macro_rules! bail_if_invalid_type { + ($str:expr, $span:expr) => { + if is_js_keyword($str) { + return Err(Diagnostic::span_error($span, "collides with js/ts keyword")); + } + bail_if_incl_illegal_char!($str, $span); + }; +} + #[derive(Default)] struct AttributeParseState { parsed: Cell, @@ -1154,24 +1196,11 @@ fn function_from_decl( syn::ReturnType::Type(_, ty) => Some(ast::FunctionReturnData { r#type: replace_self(*ty), js_type: ret_ty_override.as_ref().map_or(Ok(None), |(ty, span)| { - if is_js_keyword(ty) { - return Err(Diagnostic::span_error(*span, "collides with js/ts keyword")); - } - if contains_js_comment_close(ty) { - return Err(Diagnostic::span_error( - *span, - "contains illegal comment close syntax", - )); - } + bail_if_invalid_type!(ty, *span); Ok(Some(ty.to_string())) })?, desc: ret_desc.as_ref().map_or(Ok(None), |(desc, span)| { - if contains_js_comment_close(desc) { - return Err(Diagnostic::span_error( - *span, - "contains illegal comment close syntax", - )); - } + bail_if_incl_illegal_char!(desc, *span); Ok(Some(desc.to_string())) })?, }), @@ -1256,46 +1285,19 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn js_name: attrs .js_name() .map_or(Ok(None), |(js_name_override, span)| { - if is_js_keyword(js_name_override) { - return Err(Diagnostic::span_error( - span, - "collides with js/ts keyword", - )); - } - if !is_valid_ident(js_name_override) { - return Err(Diagnostic::span_error( - span, - "invalid js/ts argument identifier", - )); - } + bail_if_invalid_ident!(js_name_override, span); Ok(Some(js_name_override.to_string())) })?, js_type: attrs .unchecked_param_type() .map_or(Ok(None), |(ty, span)| { - if is_js_keyword(ty) { - return Err(Diagnostic::span_error( - span, - "collides with js/ts keyword", - )); - } - if contains_js_comment_close(ty) { - return Err(Diagnostic::span_error( - span, - "contains illegal comment close syntax", - )); - } + bail_if_invalid_type!(ty, span); Ok(Some(ty.to_string())) })?, desc: attrs .param_description() .map_or(Ok(None), |(description, span)| { - if contains_js_comment_close(description) { - return Err(Diagnostic::span_error( - span, - "contains illegal comment close syntax", - )); - } + bail_if_incl_illegal_char!(description, span); Ok(Some(description.to_string())) })?, }; From bae360a588e99d21d64c42da1c7fbf32ca120d2a Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Sun, 12 Jan 2025 00:37:04 +0000 Subject: [PATCH 45/50] fix error msg --- crates/macro-support/src/parser.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 1397a60964d..2775e92bb77 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -132,10 +132,7 @@ macro_rules! bail_if_incl_illegal_char { macro_rules! bail_if_invalid_ident { ($str:expr, $span:expr) => { if is_invalid_ident($str) { - return Err(Diagnostic::span_error( - $span, - "invalid js/ts argument identifier", - )); + return Err(Diagnostic::span_error($span, "invalid js/ts identifier")); } }; } From 89e500e29ceba1ebce3f01814dad3069c2e51983 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Sun, 12 Jan 2025 00:38:36 +0000 Subject: [PATCH 46/50] fix ui test --- .../macro/ui-tests/invalid-fn-arg-name.stderr | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/macro/ui-tests/invalid-fn-arg-name.stderr b/crates/macro/ui-tests/invalid-fn-arg-name.stderr index 315e6bbdba2..0f03a8d5e1f 100644 --- a/crates/macro/ui-tests/invalid-fn-arg-name.stderr +++ b/crates/macro/ui-tests/invalid-fn-arg-name.stderr @@ -1,70 +1,70 @@ -error: invalid js/ts argument identifier +error: invalid js/ts identifier --> ui-tests/invalid-fn-arg-name.rs:5:30 | 5 | #[wasm_bindgen(js_name = "*firstArg")] arg: u32, | ^^^^^^^^^^^ -error: invalid js/ts argument identifier +error: invalid js/ts identifier --> ui-tests/invalid-fn-arg-name.rs:12:30 | 12 | #[wasm_bindgen(js_name = "#firstArg")] arg: u32, | ^^^^^^^^^^^ -error: invalid js/ts argument identifier +error: invalid js/ts identifier --> ui-tests/invalid-fn-arg-name.rs:19:30 | 19 | #[wasm_bindgen(js_name = "firstArg#")] arg: u32, | ^^^^^^^^^^^ -error: invalid js/ts argument identifier +error: invalid js/ts identifier --> ui-tests/invalid-fn-arg-name.rs:26:30 | 26 | #[wasm_bindgen(js_name = "first-Arg")] arg: u32, | ^^^^^^^^^^^ -error: invalid js/ts argument identifier +error: invalid js/ts identifier --> ui-tests/invalid-fn-arg-name.rs:33:30 | 33 | #[wasm_bindgen(js_name = "--firstArg")] arg: u32, | ^^^^^^^^^^^^ -error: invalid js/ts argument identifier +error: invalid js/ts identifier --> ui-tests/invalid-fn-arg-name.rs:40:30 | 40 | #[wasm_bindgen(js_name = " first Arg")] arg: u32, | ^^^^^^^^^^^^ -error: invalid js/ts argument identifier +error: invalid js/ts identifier --> ui-tests/invalid-fn-arg-name.rs:52:34 | 52 | #[wasm_bindgen(js_name = "(firstArg)")] arg: u32, | ^^^^^^^^^^^^ -error: invalid js/ts argument identifier +error: invalid js/ts identifier --> ui-tests/invalid-fn-arg-name.rs:59:34 | 59 | #[wasm_bindgen(js_name = "[firstArg]")] arg: u32, | ^^^^^^^^^^^^ -error: invalid js/ts argument identifier +error: invalid js/ts identifier --> ui-tests/invalid-fn-arg-name.rs:66:34 | 66 | #[wasm_bindgen(js_name = "")] arg: u32, | ^^^^^^^^^^^^ -error: invalid js/ts argument identifier +error: invalid js/ts identifier --> ui-tests/invalid-fn-arg-name.rs:73:34 | 73 | #[wasm_bindgen(js_name = "firstArg+")] arg: u32, | ^^^^^^^^^^^ -error: invalid js/ts argument identifier +error: invalid js/ts identifier --> ui-tests/invalid-fn-arg-name.rs:80:34 | 80 | #[wasm_bindgen(js_name = "@firstArg")] arg: u32, | ^^^^^^^^^^^ -error: invalid js/ts argument identifier +error: invalid js/ts identifier --> ui-tests/invalid-fn-arg-name.rs:87:34 | 87 | #[wasm_bindgen(js_name = "!firstArg")] arg: u32, From 129c471775fc7c3db27764ebcd92b5af94c6f676 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Sun, 12 Jan 2025 01:52:22 +0000 Subject: [PATCH 47/50] Update parser.rs --- crates/macro-support/src/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 2775e92bb77..6a9138e3b2a 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -115,7 +115,7 @@ fn is_invalid_ident(str: &str) -> bool { } /// Immediately fail and return an Err if the given string -/// contains illegal JS/TS comment close syntax ('*/') +/// contains illegal JS/TS comment close syntax ("*/") macro_rules! bail_if_incl_illegal_char { ($str:expr, $span:expr) => { if contains_js_comment_close($str) { From 83d7d7f7560dc44745f147819ae72b30e226cf8e Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Sun, 12 Jan 2025 02:58:19 +0000 Subject: [PATCH 48/50] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a6c82a9484..f2ae5738e48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Added -* Added Function Attributes, which are attributes that can be used on an exporting function to specify/override the function's argument name (`js_name`), type (`unchecked_param_type`) and description (`param_description`) as well as return type (`unchecked_return_type`) and description (`return_description`) for its bindings. +* Added Function Attributes, which are attributes that can be used on an exporting function to specify/override the function's argument name (`js_name`), type (`unchecked_param_type`) and description (`param_description`) as well as its return type (`unchecked_return_type`) and description (`return_description`) for its bindings. [#4394](https://github.com/rustwasm/wasm-bindgen/pull/4394) * Add a `copy_to_uninit()` method to all `TypedArray`s. It takes `&mut [MaybeUninit]` and returns `&mut [T]`. @@ -22,7 +22,7 @@ ### Changed -* Moved `crates/cli-support/src/js/identifier.rs` to `crates/shared/src/identifier.rs`. +* Moved `crates/cli-support/src/js/identifier.rs` to `crates/shared/src/identifier.rs` as well as `unicode-ident` dependency. [#4394](https://github.com/rustwasm/wasm-bindgen/pull/4394) * Optional parameters are now typed as `T | undefined | null` to reflect the actual JS behavior. From 6c5e576e6615d18c2e4c366a9ff81d0d75b85dd8 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 12 Jan 2025 09:35:33 +0100 Subject: [PATCH 49/50] Adjust changelog and documentation --- CHANGELOG.md | 5 +- guide/src/SUMMARY.md | 2 + .../attributes/on-rust-exports/description.md | 60 +++++++++++ .../on-rust-exports/function-attributes.md | 102 ------------------ .../attributes/on-rust-exports/js_name.md | 48 +++++++++ .../on-rust-exports/unchecked_type.md | 53 +++++++++ 6 files changed, 164 insertions(+), 106 deletions(-) create mode 100644 guide/src/reference/attributes/on-rust-exports/description.md delete mode 100644 guide/src/reference/attributes/on-rust-exports/function-attributes.md create mode 100644 guide/src/reference/attributes/on-rust-exports/unchecked_type.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f2ae5738e48..c2a06670873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Added -* Added Function Attributes, which are attributes that can be used on an exporting function to specify/override the function's argument name (`js_name`), type (`unchecked_param_type`) and description (`param_description`) as well as its return type (`unchecked_return_type`) and description (`return_description`) for its bindings. +* Add attributes to overwrite return (``unchecked_return_type`) and parameter types (`unchecked_param_type`), descriptions (`return_description` and `param_description`) as well as parameter names (`js_name`) for exported functions and methods. See the guide for more details. [#4394](https://github.com/rustwasm/wasm-bindgen/pull/4394) * Add a `copy_to_uninit()` method to all `TypedArray`s. It takes `&mut [MaybeUninit]` and returns `&mut [T]`. @@ -22,9 +22,6 @@ ### Changed -* Moved `crates/cli-support/src/js/identifier.rs` to `crates/shared/src/identifier.rs` as well as `unicode-ident` dependency. - [#4394](https://github.com/rustwasm/wasm-bindgen/pull/4394) - * Optional parameters are now typed as `T | undefined | null` to reflect the actual JS behavior. [#4188](https://github.com/rustwasm/wasm-bindgen/pull/4188) diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index c3b58139d00..1795bf6f011 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -96,6 +96,8 @@ - [`inspectable`](./reference/attributes/on-rust-exports/inspectable.md) - [`skip_typescript`](./reference/attributes/on-rust-exports/skip_typescript.md) - [`getter_with_clone`](./reference/attributes/on-rust-exports/getter_with_clone.md) + - [`unchecked_return_type` and `unchecked_param_type`](./reference/attributes/on-rust-exports/unchecked_type.md) + - [`return_description` and `param_description`](./reference/attributes/on-rust-exports/description.md) - [`web-sys`](./web-sys/index.md) - [Using `web-sys`](./web-sys/using-web-sys.md) diff --git a/guide/src/reference/attributes/on-rust-exports/description.md b/guide/src/reference/attributes/on-rust-exports/description.md new file mode 100644 index 00000000000..831cbb81815 --- /dev/null +++ b/guide/src/reference/attributes/on-rust-exports/description.md @@ -0,0 +1,60 @@ +# `return_description` and `param_description` + +Descriptions to return and parameter documentation can be added with `#[wasm_bindgen(return_description)]` and `#[wasm_bindgen(param_description)]`. + +```rust +/// Adds `arg1` and `arg2`. +#[wasm_bindgen(return_description = "the result of the addition of `arg1` and `arg2`")] +pub fn add( + #[wasm_bindgen(param_description = "the first number")] + arg1: u32, + #[wasm_bindgen(param_description = "the second number")] + arg2: u32, +) -> u32 { + arg1 + arg2 +} + +#[wasm_bindgen] +pub struct FooList { + // properties +} + +#[wasm_bindgen] +impl FooList { + /// Returns the number at the given index. + #[wasm_bindgen(return_description = "the number at the given index")] + pub fn number( + &self, + #[wasm_bindgen(param_description = "the index of the number to be returned")] + index: u32, + ) -> u32 { + // function body + } +} +``` + +Which will generate the following JS bindings: +```js +/** + * Adds `arg1` and `arg2`. + * + * @param {number} arg1 - the first number + * @param {number} arg2 - the second number + * @returns {number} the result of the addition of `arg1` and `arg2` + */ +export function add(arg1, arg2) { + // ... +} + +export class FooList { + /** + * Returns the number at the given index. + * + * @param {number} index - the index of the number to be returned + * @returns {number} the number at the given index + */ + number(index) { + // ... + } +} +``` diff --git a/guide/src/reference/attributes/on-rust-exports/function-attributes.md b/guide/src/reference/attributes/on-rust-exports/function-attributes.md deleted file mode 100644 index e4d435723dc..00000000000 --- a/guide/src/reference/attributes/on-rust-exports/function-attributes.md +++ /dev/null @@ -1,102 +0,0 @@ -# `function-attributes` - -By default, exported Rust functions and methods generate function signature from equivalent Rust types identifiers without any arguments and return variable documentations. However by using function attributes, it's possible to override a function's return type and argument names and types for generated bindings, as well as the ability to write specific documentation for each of them individually as desired: -- `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(return_description)]` used to override a function's return type and to specify descriptions for generated JS/TS bindings. -- `#[wasm_bindgen(js_name)]`, `#[wasm_bindgen(unchecked_param_type)]` and `#[wasm_bindgen(param_description)]` applied to a Rust function argument to override that argument's name and type and to specify descriptions for generated JS/TS bindings. - -> **NOTE**: -> Types that are provided using `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(unchecked_param_type)]` aren't checked for their contents. They will end up in function signature and JSDoc exactly as they have been specified. E.g. `#[wasm_bindgen(unchecked_return_type = "number")]` on a function returning `String` will return a `string`, not a `number`, even if the TS signature and JSDoc will say otherwise. - -Let's look at some exmaples: -```rust -/// Description for foo -#[wasm_bindgen(unchecked_return_type = "Foo", return_description = "some description for return type")] -pub async fn foo( - #[wasm_bindgen(js_name = "firstArg", param_description = "some description for firstArg")] - arg1: String, - #[wasm_bindgen(js_name = "secondArg", unchecked_param_type = "Bar")] - arg2: JsValue, -) -> Result { - // function body -} -``` - -This will generate the following JS bindings: -```js -/** -* Description for foo -* @param {string} firstArg - some description for firstArg -* @param {Bar} secondArg -* @returns {Promise} some description for return type -*/ -export function foo(firstArg, secondArg) {}; -``` - -And will generate the following TS bindings: -```ts -/** -* Description for foo -* @param firstArg - some description for firstArg -* @param secondArg -* @returns some description for return type -*/ -export function foo(firstArg: string, secondArg: Bar): Promise; -``` - -Same thing applies to Rust struct's (and enums) impl methods and their equivalent JS/TS class methods: -```rust -/// Description for Foo -#[wasm_bindgen] -pub struct Foo { - // properties -} - -#[wasm_bindgen] -impl Foo { - /// Description for foo - #[wasm_bindgen(unchecked_return_type = "Baz", return_description = "some description for return type")] - pub fn foo( - &self, - #[wasm_bindgen(js_name = "firstArg", param_description = "some description for firstArg")] - arg1: String, - #[wasm_bindgen(js_name = "secondArg", unchecked_param_type = "Bar")] - arg2: JsValue, - ) -> JsValue { - // function body - } -} -``` - -This will generate the following JS bindings: -```js -/** -* Description for Foo -*/ -export class Foo { - /** - * Description for foo - * @param {string} firstArg - some description for firstArg - * @param {Bar} secondArg - * @returns {Baz} some description for return type - */ - foo(firstArg, secondArg) {}; -} -``` - -And will generate the following TS bindings: -```ts -/** -* Description for Foo -*/ -export class Foo { - /** - * Description for foo - * @param firstArg - some description for firstArg - * @param secondArg - * @returns some description for return type - */ - foo(firstArg: string, secondArg: Bar): Baz; -} -``` - -As shown in these examples, these attributes provide a great level of control and customization over generated bindings. But note that they can only be used on functions and methods that are being exported to JS/TS and cannot be used on the `self` argument of Rust struct/enum methods. diff --git a/guide/src/reference/attributes/on-rust-exports/js_name.md b/guide/src/reference/attributes/on-rust-exports/js_name.md index b6a883857f1..876f4106582 100644 --- a/guide/src/reference/attributes/on-rust-exports/js_name.md +++ b/guide/src/reference/attributes/on-rust-exports/js_name.md @@ -52,3 +52,51 @@ impl JsFoo { // ... } ``` + +It can also be used to rename parameters of exported functions and methods: + +```rust +#[wasm_bindgen] +pub fn foo( + #[wasm_bindgen(js_name = "firstArg")] + arg1: String, +) { + // function body +} + +#[wasm_bindgen] +pub struct Foo { + // properties +} + +#[wasm_bindgen] +impl Foo { + pub fn foo( + &self, + #[wasm_bindgen(js_name = "firstArg")] + arg1: u32, + ) { + // function body + } +} +``` + +Which will generate the following JS bindings: + +```js +/** + * @param {string} firstArg + */ +export function foo(firstArg) { + // ... +} + +export class Foo { + /** + * @param {number} firstArg + */ + foo(firstArg) { + // ... + } +} +``` diff --git a/guide/src/reference/attributes/on-rust-exports/unchecked_type.md b/guide/src/reference/attributes/on-rust-exports/unchecked_type.md new file mode 100644 index 00000000000..a1ac509bb9e --- /dev/null +++ b/guide/src/reference/attributes/on-rust-exports/unchecked_type.md @@ -0,0 +1,53 @@ +# `unchecked_return_type` and `unchecked_param_type` + +Return and parameter types of exported functions and methods can be overwritten with `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(unchecked_param_type)]`. + +> **Note**: Types that are provided using `#[wasm_bindgen(unchecked_return_type)]` and `#[wasm_bindgen(unchecked_param_type)]` aren't checked for their contents. They will end up in a function signature and JSDoc exactly as they have been specified. E.g. `#[wasm_bindgen(unchecked_return_type = "number")]` on a function returning `String` will return a `string`, not a `number`, even if the TS signature and JSDoc will say otherwise. + +```rust +#[wasm_bindgen(unchecked_return_type = "Foo")] +pub fn foo( + #[wasm_bindgen(unchecked_param_type = "Bar")] + arg1: JsValue, +) -> JsValue { + // function body +} + +#[wasm_bindgen] +pub struct Foo { + // properties +} + +#[wasm_bindgen] +impl Foo { + #[wasm_bindgen(unchecked_return_type = "Baz")] + pub fn foo( + &self, + #[wasm_bindgen(unchecked_param_type = "Bar")] + arg1: JsValue, + ) -> JsValue { + // function body + } +} +``` + +Which will generate the following JS bindings: +```js +/** + * @param {Bar} arg1 + * @returns {Foo} + */ +export function foo(arg1) { + // ... +} + +export class Foo { + /** + * @param {Bar} arg1 + * @returns {Baz} + */ + foo(arg1) { + // ... + } +} +``` From ff4064efff93c82967f6f304f09e91010219986c Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 12 Jan 2025 10:06:50 +0100 Subject: [PATCH 50/50] Replace macros with functions --- crates/macro-support/src/parser.rs | 90 ++++++++----------- .../ui-tests/illegal-char-fn-attrs.stderr | 10 +-- .../macro/ui-tests/invalid-fn-arg-name.stderr | 24 ++--- 3 files changed, 54 insertions(+), 70 deletions(-) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 6a9138e3b2a..a3cf49bffd2 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -104,48 +104,25 @@ fn is_non_value_js_keyword(keyword: &str) -> bool { JS_KEYWORDS.contains(&keyword) && !VALUE_LIKE_JS_KEYWORDS.contains(&keyword) } -/// Checks if the given string contains JS/TS comment block close syntax -fn contains_js_comment_close(str: &str) -> bool { - str.contains("*/") -} - -/// Checks if the given string is an invalid JS/TS identifier -fn is_invalid_ident(str: &str) -> bool { - is_js_keyword(str) || !is_valid_ident(str) -} - -/// Immediately fail and return an Err if the given string -/// contains illegal JS/TS comment close syntax ("*/") -macro_rules! bail_if_incl_illegal_char { - ($str:expr, $span:expr) => { - if contains_js_comment_close($str) { - return Err(Diagnostic::span_error( - $span, - "contains illegal comment close syntax", - )); - } - }; -} - -/// Immediately fail and return an Err if the -/// given string is not a valid JS/TS identifier -macro_rules! bail_if_invalid_ident { - ($str:expr, $span:expr) => { - if is_invalid_ident($str) { - return Err(Diagnostic::span_error($span, "invalid js/ts identifier")); - } - }; +/// Return an [`Err`] if the given string contains a comment close syntax (`*/``). +fn check_js_comment_close(str: &str, span: Span) -> Result<(), Diagnostic> { + if str.contains("*/") { + Err(Diagnostic::span_error( + span, + "contains comment close syntax", + )) + } else { + Ok(()) + } } -/// Immediately fail and return an Err if the -/// given string is not a valid JS/TS type -macro_rules! bail_if_invalid_type { - ($str:expr, $span:expr) => { - if is_js_keyword($str) { - return Err(Diagnostic::span_error($span, "collides with js/ts keyword")); - } - bail_if_incl_illegal_char!($str, $span); - }; +/// Return an [`Err`] if the given string is a JS keyword or contains a comment close syntax (`*/``). +fn check_invalid_type(str: &str, span: Span) -> Result<(), Diagnostic> { + if is_js_keyword(str) { + return Err(Diagnostic::span_error(span, "collides with JS keyword")); + } + check_js_comment_close(str, span)?; + Ok(()) } #[derive(Default)] @@ -1192,14 +1169,19 @@ fn function_from_decl( syn::ReturnType::Default => None, syn::ReturnType::Type(_, ty) => Some(ast::FunctionReturnData { r#type: replace_self(*ty), - js_type: ret_ty_override.as_ref().map_or(Ok(None), |(ty, span)| { - bail_if_invalid_type!(ty, *span); - Ok(Some(ty.to_string())) - })?, - desc: ret_desc.as_ref().map_or(Ok(None), |(desc, span)| { - bail_if_incl_illegal_char!(desc, *span); - Ok(Some(desc.to_string())) - })?, + js_type: ret_ty_override + .as_ref() + .map_or::, _>(Ok(None), |(ty, span)| { + check_invalid_type(ty, *span)?; + Ok(Some(ty.to_string())) + })?, + desc: ret_desc.as_ref().map_or::, _>( + Ok(None), + |(desc, span)| { + check_js_comment_close(desc, *span)?; + Ok(Some(desc.to_string())) + }, + )?, }), }; // error if there were description or type override specified for @@ -1282,19 +1264,21 @@ fn extract_args_attrs(sig: &mut syn::Signature) -> Result, Diagn js_name: attrs .js_name() .map_or(Ok(None), |(js_name_override, span)| { - bail_if_invalid_ident!(js_name_override, span); + if is_js_keyword(js_name_override) || !is_valid_ident(js_name_override) { + return Err(Diagnostic::span_error(span, "invalid JS identifier")); + } Ok(Some(js_name_override.to_string())) })?, js_type: attrs .unchecked_param_type() - .map_or(Ok(None), |(ty, span)| { - bail_if_invalid_type!(ty, span); + .map_or::, _>(Ok(None), |(ty, span)| { + check_invalid_type(ty, span)?; Ok(Some(ty.to_string())) })?, desc: attrs .param_description() - .map_or(Ok(None), |(description, span)| { - bail_if_incl_illegal_char!(description, span); + .map_or::, _>(Ok(None), |(description, span)| { + check_js_comment_close(description, span)?; Ok(Some(description.to_string())) })?, }; diff --git a/crates/macro/ui-tests/illegal-char-fn-attrs.stderr b/crates/macro/ui-tests/illegal-char-fn-attrs.stderr index d03f1e56c73..ef9de562305 100644 --- a/crates/macro/ui-tests/illegal-char-fn-attrs.stderr +++ b/crates/macro/ui-tests/illegal-char-fn-attrs.stderr @@ -1,28 +1,28 @@ -error: contains illegal comment close syntax +error: contains comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:5:43 | 5 | #[wasm_bindgen(unchecked_param_type = "abcd */firstArg")] arg1: u32, | ^^^^^^^^^^^^^^^^^ -error: contains illegal comment close syntax +error: contains comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:12:43 | 12 | #[wasm_bindgen(unchecked_param_type = "num*/ber")] arg1: u32, | ^^^^^^^^^^ -error: contains illegal comment close syntax +error: contains comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:19:40 | 19 | #[wasm_bindgen(param_description = "/* some description */")] arg1: u32, | ^^^^^^^^^^^^^^^^^^^^^^^^ -error: contains illegal comment close syntax +error: contains comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:24:37 | 24 | #[wasm_bindgen(return_description = "*/ some description")] | ^^^^^^^^^^^^^^^^^^^^^ -error: contains illegal comment close syntax +error: contains comment close syntax --> ui-tests/illegal-char-fn-attrs.rs:29:40 | 29 | #[wasm_bindgen(unchecked_return_type = "number */ abcd")] diff --git a/crates/macro/ui-tests/invalid-fn-arg-name.stderr b/crates/macro/ui-tests/invalid-fn-arg-name.stderr index 0f03a8d5e1f..bee7bd83a04 100644 --- a/crates/macro/ui-tests/invalid-fn-arg-name.stderr +++ b/crates/macro/ui-tests/invalid-fn-arg-name.stderr @@ -1,70 +1,70 @@ -error: invalid js/ts identifier +error: invalid JS identifier --> ui-tests/invalid-fn-arg-name.rs:5:30 | 5 | #[wasm_bindgen(js_name = "*firstArg")] arg: u32, | ^^^^^^^^^^^ -error: invalid js/ts identifier +error: invalid JS identifier --> ui-tests/invalid-fn-arg-name.rs:12:30 | 12 | #[wasm_bindgen(js_name = "#firstArg")] arg: u32, | ^^^^^^^^^^^ -error: invalid js/ts identifier +error: invalid JS identifier --> ui-tests/invalid-fn-arg-name.rs:19:30 | 19 | #[wasm_bindgen(js_name = "firstArg#")] arg: u32, | ^^^^^^^^^^^ -error: invalid js/ts identifier +error: invalid JS identifier --> ui-tests/invalid-fn-arg-name.rs:26:30 | 26 | #[wasm_bindgen(js_name = "first-Arg")] arg: u32, | ^^^^^^^^^^^ -error: invalid js/ts identifier +error: invalid JS identifier --> ui-tests/invalid-fn-arg-name.rs:33:30 | 33 | #[wasm_bindgen(js_name = "--firstArg")] arg: u32, | ^^^^^^^^^^^^ -error: invalid js/ts identifier +error: invalid JS identifier --> ui-tests/invalid-fn-arg-name.rs:40:30 | 40 | #[wasm_bindgen(js_name = " first Arg")] arg: u32, | ^^^^^^^^^^^^ -error: invalid js/ts identifier +error: invalid JS identifier --> ui-tests/invalid-fn-arg-name.rs:52:34 | 52 | #[wasm_bindgen(js_name = "(firstArg)")] arg: u32, | ^^^^^^^^^^^^ -error: invalid js/ts identifier +error: invalid JS identifier --> ui-tests/invalid-fn-arg-name.rs:59:34 | 59 | #[wasm_bindgen(js_name = "[firstArg]")] arg: u32, | ^^^^^^^^^^^^ -error: invalid js/ts identifier +error: invalid JS identifier --> ui-tests/invalid-fn-arg-name.rs:66:34 | 66 | #[wasm_bindgen(js_name = "")] arg: u32, | ^^^^^^^^^^^^ -error: invalid js/ts identifier +error: invalid JS identifier --> ui-tests/invalid-fn-arg-name.rs:73:34 | 73 | #[wasm_bindgen(js_name = "firstArg+")] arg: u32, | ^^^^^^^^^^^ -error: invalid js/ts identifier +error: invalid JS identifier --> ui-tests/invalid-fn-arg-name.rs:80:34 | 80 | #[wasm_bindgen(js_name = "@firstArg")] arg: u32, | ^^^^^^^^^^^ -error: invalid js/ts identifier +error: invalid JS identifier --> ui-tests/invalid-fn-arg-name.rs:87:34 | 87 | #[wasm_bindgen(js_name = "!firstArg")] arg: u32,