From 292696fc424e9f44fead55f182ef35bc6350a347 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Sun, 16 Nov 2025 13:16:53 +0800 Subject: [PATCH 01/13] feat(macros): add support for alias command macro in tauri-macros #14173 --- .changes/add-macros-allow-alias-command.md | 5 ++ crates/tauri-macros/src/command/handler.rs | 18 +++++- crates/tauri-macros/src/command/wrapper.rs | 72 +++++++++++++++++++--- 3 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 .changes/add-macros-allow-alias-command.md diff --git a/.changes/add-macros-allow-alias-command.md b/.changes/add-macros-allow-alias-command.md new file mode 100644 index 000000000000..e5f9137a6710 --- /dev/null +++ b/.changes/add-macros-allow-alias-command.md @@ -0,0 +1,5 @@ +--- +"tauri-macros": minor:feat +--- + +Add support for alias command macro in tauri-macros \ No newline at end of file diff --git a/crates/tauri-macros/src/command/handler.rs b/crates/tauri-macros/src/command/handler.rs index add699b73da2..566481038b84 100644 --- a/crates/tauri-macros/src/command/handler.rs +++ b/crates/tauri-macros/src/command/handler.rs @@ -155,10 +155,26 @@ impl From for proc_macro::TokenStream { .into_iter() .map(|def| (def.path, def.attrs)) .unzip(); + // Build the module-qualified paths for the command name constants emitted by the wrapper macro. + let command_name_consts: Vec = paths + .iter() + .zip(commands.iter()) + .map(|(p, name)| { + let mut p = p.clone(); + let last = p + .segments + .last_mut() + .expect("path has at least one segment"); + let upper = name.to_string().to_uppercase(); + last.ident = format_ident!("__TAURI_COMMAND_NAME_{}", upper); + p + }) + .collect(); + quote::quote!(move |#invoke| { let #cmd = #invoke.message.command(); match #cmd { - #(#(#attrs)* stringify!(#commands) => #wrappers!(#paths, #invoke),)* + #(#(#attrs)* #command_name_consts => #wrappers!(#paths, #invoke),)* _ => { return false; }, diff --git a/crates/tauri-macros/src/command/wrapper.rs b/crates/tauri-macros/src/command/wrapper.rs index 261257d639c7..41baf7ef00b6 100644 --- a/crates/tauri-macros/src/command/wrapper.rs +++ b/crates/tauri-macros/src/command/wrapper.rs @@ -40,6 +40,7 @@ struct WrapperAttributes { root: TokenStream2, execution_context: ExecutionContext, argument_case: ArgumentCase, + alias: Option, } impl Parse for WrapperAttributes { @@ -48,6 +49,7 @@ impl Parse for WrapperAttributes { root: quote!(::tauri), execution_context: ExecutionContext::Blocking, argument_case: ArgumentCase::Camel, + alias: None, }; let attrs = Punctuated::::parse_terminated(input)?; @@ -74,6 +76,19 @@ impl Parse for WrapperAttributes { } }; } + } else if v.path.is_ident("alias") { + if let Expr::Lit(ExprLit { + lit: Lit::Str(s), .. + }) = v.value + { + let lit = s.value(); + wrapper_attributes.alias = Some(quote!(#lit)); + } else { + return Err(syn::Error::new( + v.span(), + "expected string literal for alias", + )); + } } else if v.path.is_ident("root") { if let Expr::Lit(ExprLit { lit: Lit::Str(s), @@ -94,7 +109,7 @@ impl Parse for WrapperAttributes { WrapperAttributeKind::Meta(Meta::Path(_)) => { return Err(syn::Error::new( input.span(), - "unexpected input, expected one of `rename_all`, `root`, `async`", + "unexpected input, expected one of `rename_all`, `alias`, `root`, `async`", )); } WrapperAttributeKind::Async => { @@ -138,10 +153,16 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { attrs.execution_context = ExecutionContext::Async; } - // macros used with `pub use my_macro;` need to be exported with `#[macro_export]` - let maybe_macro_export = match &function.vis { - Visibility::Public(_) | Visibility::Restricted(_) => quote!(#[macro_export]), - _ => TokenStream2::default(), + // macros used with `pub use my_macro;` need to be exported with `#[macro_export]`. + // To avoid crate-root name collisions for same-named commands across modules, + // only export non-renamed commands at crate root. Renamed commands remain module-scoped. + let maybe_macro_export = if attrs.alias.is_none() { + match &function.vis { + Visibility::Public(_) | Visibility::Restricted(_) => quote!(#[macro_export]), + _ => TokenStream2::default(), + } + } else { + TokenStream2::default() }; let invoke = Invoke { @@ -270,6 +291,28 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { TokenStream2::default() }; + // For renamed commands (no crate-root export), restrict alias visibility to crate-only + // to avoid public re-export errors for non-exported macros. + let alias_visibility = if attrs.alias.is_some() { + quote!(pub(crate)) + } else { + quote!(#visibility) + }; + + // Always define a hidden constant holding the externally invoked command name. + // This lets the handler match on the renamed string while the original function + // identifier remains usable in `generate_handler![original_fn_name]`. + let command_name_const_ident = { + let upper = function.sig.ident.to_string().to_uppercase(); + format_ident!("__TAURI_COMMAND_NAME_{}", upper) + }; + let command_name_const_value = if let Some(ref alias) = attrs.alias { + quote!(#alias) + } else { + let ident = &function.sig.ident; + quote!(stringify!(#ident)) + }; + // Rely on rust 2018 edition to allow importing a macro from a path. quote!( #async_command_check @@ -277,6 +320,11 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { #maybe_allow_unused #function + // Command name constant used by the handler for pattern matching. + #[doc(hidden)] + #maybe_allow_unused + pub const #command_name_const_ident: &str = #command_name_const_value; + #maybe_allow_unused #maybe_macro_export #[doc(hidden)] @@ -303,7 +351,7 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { // allow the macro to be resolved with the same path as the command function #[allow(unused_imports)] - #visibility use #wrapper; + #alias_visibility use #wrapper; ) .into() } @@ -467,11 +515,21 @@ fn parse_arg( } let root = &attributes.root; + let command_name = if let Some(r) = &attributes.alias { + let r_string = match r.clone().into_iter().next() { + Some(proc_macro2::TokenTree::Literal(lit)) => lit.to_string(), + Some(proc_macro2::TokenTree::Ident(ident)) => ident.to_string(), + _ => quote!(#r).to_string(), + }; + quote!(#r_string) + } else { + quote!(stringify!(#command)) + }; Ok(quote!(#root::ipc::CommandArg::from_command( #root::ipc::CommandItem { plugin: #plugin_name, - name: stringify!(#command), + name: #command_name, key: #key, message: &#message, acl: &#acl, From 78d0a17032b1f1127bed96b69176449bad7cab65 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Mon, 1 Dec 2025 23:18:20 +0800 Subject: [PATCH 02/13] feat(macro): rename alias command to improve clarity in tauri-macros --- .changes/add-macros-allow-alias-command.md | 5 ---- .changes/add-macros-allow-rename-command.md | 5 ++++ crates/tauri-macros/src/command/wrapper.rs | 26 ++++++++++----------- 3 files changed, 18 insertions(+), 18 deletions(-) delete mode 100644 .changes/add-macros-allow-alias-command.md create mode 100644 .changes/add-macros-allow-rename-command.md diff --git a/.changes/add-macros-allow-alias-command.md b/.changes/add-macros-allow-alias-command.md deleted file mode 100644 index e5f9137a6710..000000000000 --- a/.changes/add-macros-allow-alias-command.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri-macros": minor:feat ---- - -Add support for alias command macro in tauri-macros \ No newline at end of file diff --git a/.changes/add-macros-allow-rename-command.md b/.changes/add-macros-allow-rename-command.md new file mode 100644 index 000000000000..d5d316126fcb --- /dev/null +++ b/.changes/add-macros-allow-rename-command.md @@ -0,0 +1,5 @@ +--- +"tauri-macros": minor:feat +--- + +Add support for rename command macro in tauri-macros \ No newline at end of file diff --git a/crates/tauri-macros/src/command/wrapper.rs b/crates/tauri-macros/src/command/wrapper.rs index 41baf7ef00b6..deef6e89adba 100644 --- a/crates/tauri-macros/src/command/wrapper.rs +++ b/crates/tauri-macros/src/command/wrapper.rs @@ -40,7 +40,7 @@ struct WrapperAttributes { root: TokenStream2, execution_context: ExecutionContext, argument_case: ArgumentCase, - alias: Option, + rename: Option, } impl Parse for WrapperAttributes { @@ -49,7 +49,7 @@ impl Parse for WrapperAttributes { root: quote!(::tauri), execution_context: ExecutionContext::Blocking, argument_case: ArgumentCase::Camel, - alias: None, + rename: None, }; let attrs = Punctuated::::parse_terminated(input)?; @@ -76,17 +76,17 @@ impl Parse for WrapperAttributes { } }; } - } else if v.path.is_ident("alias") { + } else if v.path.is_ident("rename") { if let Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) = v.value { let lit = s.value(); - wrapper_attributes.alias = Some(quote!(#lit)); + wrapper_attributes.rename = Some(quote!(#lit)); } else { return Err(syn::Error::new( v.span(), - "expected string literal for alias", + "expected string literal for rename", )); } } else if v.path.is_ident("root") { @@ -109,7 +109,7 @@ impl Parse for WrapperAttributes { WrapperAttributeKind::Meta(Meta::Path(_)) => { return Err(syn::Error::new( input.span(), - "unexpected input, expected one of `rename_all`, `alias`, `root`, `async`", + "unexpected input, expected one of `rename_all`, `rename`, `root`, `async`", )); } WrapperAttributeKind::Async => { @@ -156,7 +156,7 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { // macros used with `pub use my_macro;` need to be exported with `#[macro_export]`. // To avoid crate-root name collisions for same-named commands across modules, // only export non-renamed commands at crate root. Renamed commands remain module-scoped. - let maybe_macro_export = if attrs.alias.is_none() { + let maybe_macro_export = if attrs.rename.is_none() { match &function.vis { Visibility::Public(_) | Visibility::Restricted(_) => quote!(#[macro_export]), _ => TokenStream2::default(), @@ -291,9 +291,9 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { TokenStream2::default() }; - // For renamed commands (no crate-root export), restrict alias visibility to crate-only + // For renamed commands (no crate-root export), restrict rename visibility to crate-only // to avoid public re-export errors for non-exported macros. - let alias_visibility = if attrs.alias.is_some() { + let rename_visibility = if attrs.rename.is_some() { quote!(pub(crate)) } else { quote!(#visibility) @@ -306,8 +306,8 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { let upper = function.sig.ident.to_string().to_uppercase(); format_ident!("__TAURI_COMMAND_NAME_{}", upper) }; - let command_name_const_value = if let Some(ref alias) = attrs.alias { - quote!(#alias) + let command_name_const_value = if let Some(ref rename) = attrs.rename { + quote!(#rename) } else { let ident = &function.sig.ident; quote!(stringify!(#ident)) @@ -351,7 +351,7 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { // allow the macro to be resolved with the same path as the command function #[allow(unused_imports)] - #alias_visibility use #wrapper; + #rename_visibility use #wrapper; ) .into() } @@ -515,7 +515,7 @@ fn parse_arg( } let root = &attributes.root; - let command_name = if let Some(r) = &attributes.alias { + let command_name = if let Some(r) = &attributes.rename { let r_string = match r.clone().into_iter().next() { Some(proc_macro2::TokenTree::Literal(lit)) => lit.to_string(), Some(proc_macro2::TokenTree::Ident(ident)) => ident.to_string(), From 895cf58f972a1c8fa91a3dbbad6fa9ef5180435e Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Tue, 2 Dec 2025 00:11:50 +0800 Subject: [PATCH 03/13] feat(wrapper): refactor rename handling in WrapperAttributes for improved clarity --- crates/tauri-macros/src/command/wrapper.rs | 30 ++++++++++++---------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/crates/tauri-macros/src/command/wrapper.rs b/crates/tauri-macros/src/command/wrapper.rs index deef6e89adba..be7e4fd93046 100644 --- a/crates/tauri-macros/src/command/wrapper.rs +++ b/crates/tauri-macros/src/command/wrapper.rs @@ -6,7 +6,7 @@ use std::{env::var, sync::OnceLock}; use heck::{ToLowerCamelCase, ToSnakeCase}; use proc_macro::TokenStream; -use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use proc_macro2::{Ident, Literal, Span, TokenStream as TokenStream2}; use quote::{format_ident, quote, quote_spanned}; use syn::{ ext::IdentExt, @@ -40,7 +40,7 @@ struct WrapperAttributes { root: TokenStream2, execution_context: ExecutionContext, argument_case: ArgumentCase, - rename: Option, + rename: RenamePolicy, } impl Parse for WrapperAttributes { @@ -49,7 +49,7 @@ impl Parse for WrapperAttributes { root: quote!(::tauri), execution_context: ExecutionContext::Blocking, argument_case: ArgumentCase::Camel, - rename: None, + rename: RenamePolicy::Keep, }; let attrs = Punctuated::::parse_terminated(input)?; @@ -82,7 +82,7 @@ impl Parse for WrapperAttributes { }) = v.value { let lit = s.value(); - wrapper_attributes.rename = Some(quote!(#lit)); + wrapper_attributes.rename = RenamePolicy::Rename(lit); } else { return Err(syn::Error::new( v.span(), @@ -135,6 +135,12 @@ enum ArgumentCase { Camel, } +/// The rename policy for the command. +enum RenamePolicy { + Keep, + Rename(String), +} + /// The bindings we attach to `tauri::Invoke`. struct Invoke { message: Ident, @@ -156,7 +162,7 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { // macros used with `pub use my_macro;` need to be exported with `#[macro_export]`. // To avoid crate-root name collisions for same-named commands across modules, // only export non-renamed commands at crate root. Renamed commands remain module-scoped. - let maybe_macro_export = if attrs.rename.is_none() { + let maybe_macro_export = if let RenamePolicy::Keep = attrs.rename { match &function.vis { Visibility::Public(_) | Visibility::Restricted(_) => quote!(#[macro_export]), _ => TokenStream2::default(), @@ -293,7 +299,7 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { // For renamed commands (no crate-root export), restrict rename visibility to crate-only // to avoid public re-export errors for non-exported macros. - let rename_visibility = if attrs.rename.is_some() { + let rename_visibility = if let RenamePolicy::Rename(_) = &attrs.rename { quote!(pub(crate)) } else { quote!(#visibility) @@ -306,7 +312,7 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { let upper = function.sig.ident.to_string().to_uppercase(); format_ident!("__TAURI_COMMAND_NAME_{}", upper) }; - let command_name_const_value = if let Some(ref rename) = attrs.rename { + let command_name_const_value = if let RenamePolicy::Rename(ref rename) = attrs.rename { quote!(#rename) } else { let ident = &function.sig.ident; @@ -515,13 +521,9 @@ fn parse_arg( } let root = &attributes.root; - let command_name = if let Some(r) = &attributes.rename { - let r_string = match r.clone().into_iter().next() { - Some(proc_macro2::TokenTree::Literal(lit)) => lit.to_string(), - Some(proc_macro2::TokenTree::Ident(ident)) => ident.to_string(), - _ => quote!(#r).to_string(), - }; - quote!(#r_string) + let command_name = if let RenamePolicy::Rename(r) = &attributes.rename { + let r_literal = Literal::string(r.as_str()); + quote!(#r_literal) } else { quote!(stringify!(#command)) }; From c9bc53df66389da6ae1b63136fc5badb8946d0ab Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Tue, 2 Dec 2025 00:24:39 +0800 Subject: [PATCH 04/13] feat(wrapper): update rename policy to use TokenStream2 for improved flexibility --- crates/tauri-macros/src/command/wrapper.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/tauri-macros/src/command/wrapper.rs b/crates/tauri-macros/src/command/wrapper.rs index be7e4fd93046..9da9663a2832 100644 --- a/crates/tauri-macros/src/command/wrapper.rs +++ b/crates/tauri-macros/src/command/wrapper.rs @@ -6,7 +6,7 @@ use std::{env::var, sync::OnceLock}; use heck::{ToLowerCamelCase, ToSnakeCase}; use proc_macro::TokenStream; -use proc_macro2::{Ident, Literal, Span, TokenStream as TokenStream2}; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; use quote::{format_ident, quote, quote_spanned}; use syn::{ ext::IdentExt, @@ -82,7 +82,7 @@ impl Parse for WrapperAttributes { }) = v.value { let lit = s.value(); - wrapper_attributes.rename = RenamePolicy::Rename(lit); + wrapper_attributes.rename = RenamePolicy::Rename(quote!(#lit)); } else { return Err(syn::Error::new( v.span(), @@ -138,7 +138,7 @@ enum ArgumentCase { /// The rename policy for the command. enum RenamePolicy { Keep, - Rename(String), + Rename(TokenStream2), } /// The bindings we attach to `tauri::Invoke`. @@ -522,8 +522,7 @@ fn parse_arg( let root = &attributes.root; let command_name = if let RenamePolicy::Rename(r) = &attributes.rename { - let r_literal = Literal::string(r.as_str()); - quote!(#r_literal) + quote!(stringify!(#r)) } else { quote!(stringify!(#command)) }; From 25bacebb14149a70f4a14c80c469e5ebda369f34 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Tue, 2 Dec 2025 00:36:06 +0800 Subject: [PATCH 05/13] feat(handler): streamline command definition parsing for improved efficiency --- crates/tauri-macros/src/command/handler.rs | 36 ++++++++++------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/crates/tauri-macros/src/command/handler.rs b/crates/tauri-macros/src/command/handler.rs index 566481038b84..1c646289bf26 100644 --- a/crates/tauri-macros/src/command/handler.rs +++ b/crates/tauri-macros/src/command/handler.rs @@ -151,25 +151,23 @@ impl From for proc_macro::TokenStream { ) -> Self { let cmd = format_ident!("__tauri_cmd__"); let invoke = format_ident!("__tauri_invoke__"); - let (paths, attrs): (Vec, Vec>) = command_defs - .into_iter() - .map(|def| (def.path, def.attrs)) - .unzip(); - // Build the module-qualified paths for the command name constants emitted by the wrapper macro. - let command_name_consts: Vec = paths - .iter() - .zip(commands.iter()) - .map(|(p, name)| { - let mut p = p.clone(); - let last = p - .segments - .last_mut() - .expect("path has at least one segment"); - let upper = name.to_string().to_uppercase(); - last.ident = format_ident!("__TAURI_COMMAND_NAME_{}", upper); - p - }) - .collect(); + let (paths, attrs, command_name_consts): (Vec, Vec>, Vec) = + command_defs + .into_iter() + .zip(commands.into_iter()) + .map(|(def, command)| { + let path = def.path; + let attrs = def.attrs; + let mut const_path = path.clone(); + let last = const_path + .segments + .last_mut() + .expect("path has at least one segment"); + let upper = command.to_string().to_uppercase(); + last.ident = format_ident!("__TAURI_COMMAND_NAME_{}", upper); + (path, attrs, const_path) + }) + .collect(); quote::quote!(move |#invoke| { let #cmd = #invoke.message.command(); From fc41cd425ce9a12e24b5f2fe9c3999d44c7cd690 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Tue, 2 Dec 2025 00:46:47 +0800 Subject: [PATCH 06/13] feat(wrapper): simplify macro export logic in wrapper function for clarity --- crates/tauri-macros/src/command/wrapper.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/tauri-macros/src/command/wrapper.rs b/crates/tauri-macros/src/command/wrapper.rs index 9da9663a2832..18856f0a86c0 100644 --- a/crates/tauri-macros/src/command/wrapper.rs +++ b/crates/tauri-macros/src/command/wrapper.rs @@ -162,13 +162,11 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { // macros used with `pub use my_macro;` need to be exported with `#[macro_export]`. // To avoid crate-root name collisions for same-named commands across modules, // only export non-renamed commands at crate root. Renamed commands remain module-scoped. - let maybe_macro_export = if let RenamePolicy::Keep = attrs.rename { - match &function.vis { - Visibility::Public(_) | Visibility::Restricted(_) => quote!(#[macro_export]), - _ => TokenStream2::default(), + let maybe_macro_export = match (&attrs.rename, &function.vis) { + (RenamePolicy::Keep, Visibility::Public(_) | Visibility::Restricted(_)) => { + quote!(#[macro_export]) } - } else { - TokenStream2::default() + _ => TokenStream2::default(), }; let invoke = Invoke { From 5f6385d6ca534d165f3821a5a036bc58b828a778 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Tue, 2 Dec 2025 05:53:48 +0800 Subject: [PATCH 07/13] fix(handler): optimize command zipping for improved readability --- crates/tauri-macros/src/command/handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/tauri-macros/src/command/handler.rs b/crates/tauri-macros/src/command/handler.rs index 1c646289bf26..d65028ce797c 100644 --- a/crates/tauri-macros/src/command/handler.rs +++ b/crates/tauri-macros/src/command/handler.rs @@ -154,7 +154,7 @@ impl From for proc_macro::TokenStream { let (paths, attrs, command_name_consts): (Vec, Vec>, Vec) = command_defs .into_iter() - .zip(commands.into_iter()) + .zip(commands) .map(|(def, command)| { let path = def.path; let attrs = def.attrs; From 5d4430709cc6fb69f4b62ac8c74861f1826bd088 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Tue, 2 Dec 2025 19:20:08 +0800 Subject: [PATCH 08/13] fix: code style compectiable with rust 1.77.2 --- crates/tauri-macros/src/command/handler.rs | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/tauri-macros/src/command/handler.rs b/crates/tauri-macros/src/command/handler.rs index d65028ce797c..e286e02b5c02 100644 --- a/crates/tauri-macros/src/command/handler.rs +++ b/crates/tauri-macros/src/command/handler.rs @@ -151,23 +151,23 @@ impl From for proc_macro::TokenStream { ) -> Self { let cmd = format_ident!("__tauri_cmd__"); let invoke = format_ident!("__tauri_invoke__"); - let (paths, attrs, command_name_consts): (Vec, Vec>, Vec) = - command_defs - .into_iter() - .zip(commands) - .map(|(def, command)| { - let path = def.path; - let attrs = def.attrs; - let mut const_path = path.clone(); - let last = const_path - .segments - .last_mut() - .expect("path has at least one segment"); - let upper = command.to_string().to_uppercase(); - last.ident = format_ident!("__TAURI_COMMAND_NAME_{}", upper); - (path, attrs, const_path) - }) - .collect(); + let mut paths: Vec = Vec::new(); + let mut attrs: Vec> = Vec::new(); + let mut command_name_consts: Vec = Vec::new(); + for (def, command) in command_defs.into_iter().zip(commands) { + let path = def.path; + let attrs_vec = def.attrs; + let mut const_path = path.clone(); + let last = const_path + .segments + .last_mut() + .expect("path has at least one segment"); + let upper = command.to_string().to_uppercase(); + last.ident = format_ident!("__TAURI_COMMAND_NAME_{}", upper); + paths.push(path); + attrs.push(attrs_vec); + command_name_consts.push(const_path); + } quote::quote!(move |#invoke| { let #cmd = #invoke.message.command(); From 7e2890d75059ff9681574c42ba686d032ea2f829 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Mon, 29 Dec 2025 14:39:36 -0300 Subject: [PATCH 09/13] fix const not in scope when command is defined in another mod --- crates/tauri-macros/src/command/handler.rs | 18 ++++++----- crates/tauri-macros/src/command/wrapper.rs | 37 +++++++++------------- examples/commands/index.html | 4 ++- examples/commands/main.rs | 6 ++++ 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/crates/tauri-macros/src/command/handler.rs b/crates/tauri-macros/src/command/handler.rs index e286e02b5c02..33143525a7b3 100644 --- a/crates/tauri-macros/src/command/handler.rs +++ b/crates/tauri-macros/src/command/handler.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use quote::format_ident; +use quote::{format_ident, quote}; use syn::{ parse::{Parse, ParseBuffer, ParseStream}, Attribute, Ident, Path, Token, @@ -153,26 +153,28 @@ impl From for proc_macro::TokenStream { let invoke = format_ident!("__tauri_invoke__"); let mut paths: Vec = Vec::new(); let mut attrs: Vec> = Vec::new(); - let mut command_name_consts: Vec = Vec::new(); + let mut command_name_macros: Vec = Vec::new(); for (def, command) in command_defs.into_iter().zip(commands) { let path = def.path; let attrs_vec = def.attrs; - let mut const_path = path.clone(); - let last = const_path + + let mut command_name_macro_path = path.clone(); + let last = command_name_macro_path .segments .last_mut() .expect("path has at least one segment"); - let upper = command.to_string().to_uppercase(); - last.ident = format_ident!("__TAURI_COMMAND_NAME_{}", upper); + last.ident = format_ident!("__tauri_command_name_{command}"); + paths.push(path); attrs.push(attrs_vec); - command_name_consts.push(const_path); + // Call the macro to get the command name string literal + command_name_macros.push(quote!(#command_name_macro_path!())); } quote::quote!(move |#invoke| { let #cmd = #invoke.message.command(); match #cmd { - #(#(#attrs)* #command_name_consts => #wrappers!(#paths, #invoke),)* + #(#(#attrs)* #command_name_macros => #wrappers!(#paths, #invoke),)* _ => { return false; }, diff --git a/crates/tauri-macros/src/command/wrapper.rs b/crates/tauri-macros/src/command/wrapper.rs index 18856f0a86c0..7eb85510e7ad 100644 --- a/crates/tauri-macros/src/command/wrapper.rs +++ b/crates/tauri-macros/src/command/wrapper.rs @@ -160,10 +160,8 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { } // macros used with `pub use my_macro;` need to be exported with `#[macro_export]`. - // To avoid crate-root name collisions for same-named commands across modules, - // only export non-renamed commands at crate root. Renamed commands remain module-scoped. - let maybe_macro_export = match (&attrs.rename, &function.vis) { - (RenamePolicy::Keep, Visibility::Public(_) | Visibility::Restricted(_)) => { + let maybe_macro_export = match &function.vis { + Visibility::Public(_) | Visibility::Restricted(_) => { quote!(#[macro_export]) } _ => TokenStream2::default(), @@ -295,22 +293,11 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { TokenStream2::default() }; - // For renamed commands (no crate-root export), restrict rename visibility to crate-only - // to avoid public re-export errors for non-exported macros. - let rename_visibility = if let RenamePolicy::Rename(_) = &attrs.rename { - quote!(pub(crate)) - } else { - quote!(#visibility) - }; - - // Always define a hidden constant holding the externally invoked command name. + // Always define a hidden macro that returns the externally invoked command name. // This lets the handler match on the renamed string while the original function // identifier remains usable in `generate_handler![original_fn_name]`. - let command_name_const_ident = { - let upper = function.sig.ident.to_string().to_uppercase(); - format_ident!("__TAURI_COMMAND_NAME_{}", upper) - }; - let command_name_const_value = if let RenamePolicy::Rename(ref rename) = attrs.rename { + let command_name_macro_ident = format_ident!("__tauri_command_name_{}", function.sig.ident); + let command_name_value = if let RenamePolicy::Rename(ref rename) = attrs.rename { quote!(#rename) } else { let ident = &function.sig.ident; @@ -324,10 +311,16 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { #maybe_allow_unused #function - // Command name constant used by the handler for pattern matching. - #[doc(hidden)] + // Command name macro used by the handler for pattern matching. + // This macro returns the command name string literal (renamed or original). #maybe_allow_unused - pub const #command_name_const_ident: &str = #command_name_const_value; + #maybe_macro_export + #[doc(hidden)] + macro_rules! #command_name_macro_ident { + () => { + #command_name_value + }; + } #maybe_allow_unused #maybe_macro_export @@ -355,7 +348,7 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { // allow the macro to be resolved with the same path as the command function #[allow(unused_imports)] - #rename_visibility use #wrapper; + #visibility use {#wrapper, #command_name_macro_ident}; ) .into() } diff --git a/examples/commands/index.html b/examples/commands/index.html index aed95c21ca7d..6e5f7165f6b1 100644 --- a/examples/commands/index.html +++ b/examples/commands/index.html @@ -63,7 +63,9 @@

Tauri Commands

{ name: 'command_arguments_tuple_struct', args: { inlinePerson: ['ferris', 6] } - } + }, + { name: 'renamed_command' }, + { name: 'renamed_command_new' }, ] for (const command of commands) { diff --git a/examples/commands/main.rs b/examples/commands/main.rs index aa0641de093b..58b063dd6078 100644 --- a/examples/commands/main.rs +++ b/examples/commands/main.rs @@ -188,6 +188,11 @@ fn command_arguments_wild(_: Window) { println!("we saw the wildcard!") } +#[command(rename = "renamed_command_new")] +fn renamed_command() { + println!("renamed command called") +} + #[derive(Deserialize)] struct Person<'a> { name: &'a str, @@ -246,6 +251,7 @@ fn main() { future_simple_command, async_stateful_command, command_arguments_wild, + renamed_command, command_arguments_struct, simple_command_with_result, async_simple_command_snake, From c9c9f56d8f4a325bfc941a0b0951aa4a36f58786 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Mon, 29 Dec 2025 14:42:11 -0300 Subject: [PATCH 10/13] update examples --- examples/commands/commands.rs | 5 +++++ examples/commands/index.html | 2 ++ examples/commands/main.rs | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/commands/commands.rs b/examples/commands/commands.rs index 93691c59dca1..4b112e283e7d 100644 --- a/examples/commands/commands.rs +++ b/examples/commands/commands.rs @@ -25,3 +25,8 @@ pub fn simple_command(the_argument: String) { pub fn stateful_command(the_argument: Option, state: State<'_, super::MyState>) { println!("{:?} {:?}", the_argument, state.inner()); } + +#[command(rename = "renamed_command_in_mod_new")] +pub fn renamed_command_in_mod() { + println!("renamed command in mod called"); +} diff --git a/examples/commands/index.html b/examples/commands/index.html index 6e5f7165f6b1..4ad6e8c6ee6c 100644 --- a/examples/commands/index.html +++ b/examples/commands/index.html @@ -66,6 +66,8 @@

Tauri Commands

}, { name: 'renamed_command' }, { name: 'renamed_command_new' }, + { name: 'renamed_command_in_mod' }, + { name: 'renamed_command_in_mod_new' }, ] for (const command of commands) { diff --git a/examples/commands/main.rs b/examples/commands/main.rs index 58b063dd6078..ed846b7f7f4e 100644 --- a/examples/commands/main.rs +++ b/examples/commands/main.rs @@ -6,7 +6,7 @@ // we move some basic commands to a separate module just to show it works mod commands; -use commands::{cmd, invoke, message, resolver}; +use commands::{cmd, invoke, message, renamed_command_in_mod, resolver}; use serde::Deserialize; use tauri::{ @@ -252,6 +252,7 @@ fn main() { async_stateful_command, command_arguments_wild, renamed_command, + renamed_command_in_mod, command_arguments_struct, simple_command_with_result, async_simple_command_snake, From 0aa0fac3b4413497a5c5753607c9095978e97bba Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Mon, 29 Dec 2025 14:43:25 -0300 Subject: [PATCH 11/13] update change file --- .changes/add-macros-allow-rename-command.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changes/add-macros-allow-rename-command.md b/.changes/add-macros-allow-rename-command.md index d5d316126fcb..bdda4d3427d6 100644 --- a/.changes/add-macros-allow-rename-command.md +++ b/.changes/add-macros-allow-rename-command.md @@ -1,5 +1,6 @@ --- "tauri-macros": minor:feat +"tauri": minor:feat --- -Add support for rename command macro in tauri-macros \ No newline at end of file +Add support for the `rename` attribute in the `tauri::command` macro to allow renaming the command to something other than the function name. From c346c89fcbc3a3350c2d94d1f7229dfff17482f6 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Mon, 29 Dec 2025 14:45:22 -0300 Subject: [PATCH 12/13] fmt --- .github/CONTRIBUTING.md | 2 ++ examples/commands/index.html | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f0b35f439f2d..975b6d5aeac2 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -33,9 +33,11 @@ Hi! We, the maintainers, are really excited that you are interested in contribut - It's OK to have multiple small commits as you work on the PR - we will let GitHub automatically squash it before merging. - If adding new feature: + - Provide convincing reason to add this feature. Ideally you should open a suggestion issue first and have it greenlighted before working on it. - If fixing a bug: + - If you are resolving a special issue, add `(fix: #xxxx[,#xxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `fix: update entities encoding/decoding (fix #3899)`. - Provide detailed description of the bug in the PR, or link to an issue that does. diff --git a/examples/commands/index.html b/examples/commands/index.html index 4ad6e8c6ee6c..ef95405fd89a 100644 --- a/examples/commands/index.html +++ b/examples/commands/index.html @@ -67,7 +67,7 @@

Tauri Commands

{ name: 'renamed_command' }, { name: 'renamed_command_new' }, { name: 'renamed_command_in_mod' }, - { name: 'renamed_command_in_mod_new' }, + { name: 'renamed_command_in_mod_new' } ] for (const command of commands) { From a67110d182c1a3cccc5d2ae46e6c3cbe157b16ef Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Tue, 30 Dec 2025 03:02:50 +0800 Subject: [PATCH 13/13] style: fix pnpm:check errors --- .github/CONTRIBUTING.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 975b6d5aeac2..f0b35f439f2d 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -33,11 +33,9 @@ Hi! We, the maintainers, are really excited that you are interested in contribut - It's OK to have multiple small commits as you work on the PR - we will let GitHub automatically squash it before merging. - If adding new feature: - - Provide convincing reason to add this feature. Ideally you should open a suggestion issue first and have it greenlighted before working on it. - If fixing a bug: - - If you are resolving a special issue, add `(fix: #xxxx[,#xxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `fix: update entities encoding/decoding (fix #3899)`. - Provide detailed description of the bug in the PR, or link to an issue that does.