From c9409d0c90fbe63ea66cab60a91faf5201b87867 Mon Sep 17 00:00:00 2001 From: bstriker Date: Tue, 14 Oct 2025 15:50:12 -0400 Subject: [PATCH] refactor: enhance error handling macros and passthrough for improved IDE support --- .../__private/expand/attr/auto_bind_plugin.rs | 4 +- .../src/__private/expand/attr/auto_plugin.rs | 56 ++++---- .../src/__private/expand/attr/mod.rs | 14 +- .../__private/expand/derive/auto_plugin.rs | 7 +- .../src/util/macros.rs | 122 ++++++++++++++++-- 5 files changed, 154 insertions(+), 49 deletions(-) diff --git a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_bind_plugin.rs b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_bind_plugin.rs index 61fa9da4..29f2b137 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_bind_plugin.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_bind_plugin.rs @@ -1,3 +1,4 @@ +use crate::util::macros::compile_error_with; use proc_macro2::TokenStream as MacroStream; pub fn auto_bind_plugin_inner(attr: MacroStream, input: MacroStream) -> syn::Result { @@ -29,5 +30,6 @@ pub fn auto_bind_plugin_inner(attr: MacroStream, input: MacroStream) -> syn::Res } pub fn auto_bind_plugin_outer(attr: MacroStream, input: MacroStream) -> MacroStream { - auto_bind_plugin_inner(attr, input).unwrap_or_else(|err| err.to_compile_error()) + let og_input = input.clone(); + auto_bind_plugin_inner(attr, input).unwrap_or_else(|err| compile_error_with!(err, og_input)) } diff --git a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_plugin.rs b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_plugin.rs index 614f5248..03bf43f0 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_plugin.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_plugin.rs @@ -1,4 +1,4 @@ -use crate::util::macros::parse_macro_input2; +use crate::util::macros::{compile_error_with, ok_or_emit_with, parse_macro_input2_or_emit_with}; use proc_macro2::TokenStream as MacroStream; use syn::ItemFn; @@ -9,11 +9,9 @@ pub fn expand_auto_plugin(attr: MacroStream, input: MacroStream) -> MacroStream use quote::quote; use syn::spanned::Spanned; use syn::{FnArg, parse2}; - let item = parse_macro_input2!(input as ItemFn); - let params = match parse2::(attr) { - Ok(params) => params, - Err(err) => return err.into_compile_error(), - }; + let og_input = input.clone(); + let item = parse_macro_input2_or_emit_with!(input as ItemFn, og_input); + let params = ok_or_emit_with!(parse2::(attr), og_input); let vis = &item.vis; let attrs = &item.attrs; let sig = &item.sig; @@ -31,42 +29,48 @@ pub fn expand_auto_plugin(attr: MacroStream, input: MacroStream) -> MacroStream let self_arg = self_args.first(); // TODO: use helper - let app_param_ident = match resolve_app_param_name(&item, params.app_param.as_ref()) { - Ok(ident) => ident, - Err(err) => return err.into_compile_error(), - }; + let app_param_ident = ok_or_emit_with!( + resolve_app_param_name(&item, params.app_param.as_ref()), + og_input + ); if let Err(err) = require_fn_param_mutable_reference(&item, app_param_ident, "bevy app") { - return err.to_compile_error(); + return compile_error_with!(err, og_input); } let mut impl_plugin = quote! {}; let auto_plugin_hook = if let Some(self_arg) = self_arg { if params.plugin.is_some() { - return syn::Error::new( - params.plugin.span(), - "auto_plugin on trait impl can't specify plugin ident", - ) - .to_compile_error(); + return compile_error_with!( + syn::Error::new( + params.plugin.span(), + "auto_plugin on trait impl can't specify plugin ident", + ), + og_input + ); }; quote! { ::build(#self_arg, #app_param_ident); } } else { if sig.inputs.len() > 1 { - return syn::Error::new( - sig.inputs.span(), - "auto_plugin on bare fn can only accept a single parameter with the type &mut bevy::prelude::App", - ) - .to_compile_error(); + return compile_error_with!( + syn::Error::new( + sig.inputs.span(), + "auto_plugin on bare fn can only accept a single parameter with the type &mut bevy::prelude::App", + ), + og_input + ); } let Some(plugin_ident) = params.plugin else { - return syn::Error::new( - params.plugin.span(), - "auto_plugin on bare fn requires the plugin ident to be specified", - ) - .to_compile_error(); + return compile_error_with!( + syn::Error::new( + params.plugin.span(), + "auto_plugin on bare fn requires the plugin ident to be specified", + ), + og_input + ); }; impl_plugin.extend(quote! { impl ::bevy_auto_plugin::__private::shared::__private::auto_plugin_registry::bevy_app::Plugin for #plugin_ident { diff --git a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs index e6d58ff3..634887a5 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs @@ -5,7 +5,7 @@ use crate::macro_api::attributes::ItemAttributeArgs; use crate::macro_api::attributes::prelude::*; use crate::macro_api::with_plugin::{PluginBound, WithPlugin}; use crate::syntax::diagnostic::kind::item_kind; -use crate::util::macros::ok_or_return_compiler_error; +use crate::util::macros::ok_or_emit_with; use darling::FromMeta; use proc_macro2::{Ident, Span, TokenStream as MacroStream}; use quote::{format_ident, quote}; @@ -56,20 +56,20 @@ where let input = input.into(); // need to clone input so we can pass through input untouched for optimal IDE support - let item: Item = ok_or_return_compiler_error!(parse2(input.clone())); + let item: Item = ok_or_emit_with!(parse2(input.clone()), input); - let err_msg = format!("Attribute macro is not allowed on {}", item_kind(&item)); - let ident = ok_or_return_compiler_error!( + let ident = ok_or_emit_with!( resolve_ident(&item).map_err(|e| { // make sure the call_site span is used instead so the user knows what attribute caused the error syn::Error::new(Span::call_site(), e) }), - err_msg + input, + format!("Attribute macro is not allowed on {}", item_kind(&item)) ); - let args = ok_or_return_compiler_error!(parse_attr(attr)); + let args = ok_or_emit_with!(parse_attr(attr), input); - let output = ok_or_return_compiler_error!(body(ident, args, &item)); + let output = ok_or_emit_with!(body(ident, args, &item), input); quote! { #input diff --git a/crates/bevy_auto_plugin_shared/src/__private/expand/derive/auto_plugin.rs b/crates/bevy_auto_plugin_shared/src/__private/expand/derive/auto_plugin.rs index 71d7377d..d707f3e4 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/expand/derive/auto_plugin.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/expand/derive/auto_plugin.rs @@ -1,4 +1,4 @@ -use crate::util::macros::parse_macro_input2; +use crate::util::macros::{ok_or_emit, parse_macro_input2}; use proc_macro2::TokenStream as MacroStream; pub fn expand_derive_auto_plugin(input: MacroStream) -> MacroStream { @@ -33,10 +33,7 @@ pub fn expand_derive_auto_plugin(input: MacroStream) -> MacroStream { .collect() }; for full_name in full_names { - let path_with_generics = match syn::parse_str::(&full_name) { - Ok(p) => p, - Err(err) => return err.into_compile_error(), - }; + let path_with_generics = ok_or_emit!(syn::parse_str::(&full_name)); auto_plugin_implemented = true; diff --git a/crates/bevy_auto_plugin_shared/src/util/macros.rs b/crates/bevy_auto_plugin_shared/src/util/macros.rs index 011cdb81..b86067a1 100644 --- a/crates/bevy_auto_plugin_shared/src/util/macros.rs +++ b/crates/bevy_auto_plugin_shared/src/util/macros.rs @@ -1,22 +1,52 @@ -macro_rules! ok_or_return_compiler_error { - // Case: expression, message - ($expr:expr, $message:expr) => {{ +macro_rules! compile_error_with { + ($err:expr, $user_tokens:expr $(,)?) => {{ + let ce = $err.to_compile_error(); + let tokens = $user_tokens; + ::quote::quote!( #ce #tokens ) + }}; +} + +macro_rules! ok_or_emit { + // Case: expression, tokens, message + ($expr:expr, $message:expr $(,)?) => {{ let message = $message; match $expr { Ok(v) => v, Err(e) => { - return syn::Error::new(e.span(), format!("{message}: {e}")) - .to_compile_error() - .into(); + return ::syn::Error::new(e.span(), format!("{message}: {e}")).to_compile_error(); } } }}; - // Case: Only expression + // Case: Only expression + tokens ($expr:expr) => {{ + match $expr { + Ok(v) => v, + Err(e) => return e.to_compile_error(), + } + }}; +} + +macro_rules! ok_or_emit_with { + // Case: Only expression + tokens + ($expr:expr, $user_tokens:expr $(,)?) => {{ match $expr { Ok(v) => v, Err(e) => { - return e.to_compile_error().into(); + let ce = e.to_compile_error(); + let tokens = $user_tokens; + return ::quote::quote!( #ce #tokens ) + } + } + }}; + // Case: expression, tokens, message + ($expr:expr, $user_tokens:expr, $message:expr $(,)?) => {{ + let message = $message; + match $expr { + Ok(v) => v, + Err(e) => { + let ce = ::syn::Error::new(e.span(), format!("{message}: {e}")).to_compile_error(); + let tokens = $user_tokens; + return ::quote::quote!( #ce #tokens ) } } }}; @@ -31,6 +61,15 @@ macro_rules! parse_macro_input2 { }}; } +macro_rules! parse_macro_input2_or_emit_with { + ($ts:ident as $ty:ty, $user_tokens:expr $(,)?) => {{ + match ::syn::parse2::<$ty>($ts) { + Ok(v) => v, + Err(e) => return $crate::util::macros::compile_error_with!(e, $user_tokens), + } + }}; +} + macro_rules! as_cargo_alias { ($name:ident) => { ::syn::Ident::new(&$name, ::proc_macro2::Span::call_site()) @@ -82,8 +121,11 @@ macro_rules! bevy_crate_path { #[rustfmt::skip] pub(crate) use { - ok_or_return_compiler_error, + compile_error_with, + ok_or_emit, + ok_or_emit_with, parse_macro_input2, + parse_macro_input2_or_emit_with, as_cargo_alias, bevy_crate_path, }; @@ -92,7 +134,8 @@ pub(crate) use { mod tests { use super::*; use internal_test_proc_macro::xtest; - use quote::ToTokens; + use proc_macro2::{Span, TokenStream}; + use quote::{ToTokens, quote}; #[xtest] fn test_bevy_crate_path() { @@ -105,4 +148,63 @@ mod tests { expected_path ) } + + #[test] + fn test_ok_or_emit_ok() { + fn process(ts: syn::Result) -> TokenStream { + ok_or_emit!(ts) + } + assert_eq!( + process(Ok(quote! { foo_bar })).to_string(), + quote! { foo_bar }.to_string() + ); + } + + #[test] + fn test_ok_or_emit_err() { + fn process(ts: syn::Result) -> TokenStream { + ok_or_emit!(ts) + } + assert_eq!( + process(Err(syn::Error::new(Span::call_site(), "error"))).to_string(), + quote! { :: core :: compile_error ! { "error" } }.to_string() + ); + } + + #[xtest] + fn test_ok_or_emit_with_ok() { + let input = quote! { + let a = 1; + let b = 2; + let c = 3; + }; + + let expected = quote! { + let foo = 4 + }; + fn process(ts: TokenStream, expected: &TokenStream) -> TokenStream { + ok_or_emit_with!(syn::Result::Ok(quote! { # expected }), ts) + } + + assert_eq!(process(input, &expected).to_string(), expected.to_string()); + } + + #[xtest] + fn test_ok_or_emit_with_err() { + let input = quote! { + let a = 1; + let b = 2; + let c = 3; + }; + fn process(ts: TokenStream) -> TokenStream { + ok_or_emit_with!(Err(syn::Error::new(Span::call_site(), "error")), ts) + } + + let expected = quote! { + :: core :: compile_error ! { "error" } + #input + }; + + assert_eq!(process(input).to_string(), expected.to_string()); + } }