From 6262c42ebab282f8bc19d233fd70e53cf4437933 Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 18 Jul 2022 07:04:30 -0400 Subject: [PATCH 1/7] add `*label::ignore_fields` attribute --- crates/bevy_derive/src/lib.rs | 2 +- crates/bevy_ecs/macros/src/lib.rs | 6 ++-- crates/bevy_macro_utils/Cargo.toml | 1 + crates/bevy_macro_utils/src/lib.rs | 45 ++++++++++++++++++++++++------ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/crates/bevy_derive/src/lib.rs b/crates/bevy_derive/src/lib.rs index 3c43776ef9961..fd7cf9e970cda 100644 --- a/crates/bevy_derive/src/lib.rs +++ b/crates/bevy_derive/src/lib.rs @@ -80,7 +80,7 @@ pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream { enum_variant_meta::derive_enum_variant_meta(input) } -#[proc_macro_derive(AppLabel)] +#[proc_macro_derive(AppLabel, attributes(app_label))] pub fn derive_app_label(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); let mut trait_path = BevyManifest::default().get_path("bevy_app"); diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 5722872df15e2..3ef43e29a4427 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -434,7 +434,7 @@ pub fn derive_world_query(input: TokenStream) -> TokenStream { derive_world_query_impl(ast) } -#[proc_macro_derive(SystemLabel)] +#[proc_macro_derive(SystemLabel, attributes(system_label))] pub fn derive_system_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut trait_path = bevy_ecs_path(); @@ -445,7 +445,7 @@ pub fn derive_system_label(input: TokenStream) -> TokenStream { derive_label(input, &trait_path) } -#[proc_macro_derive(StageLabel)] +#[proc_macro_derive(StageLabel, attributes(stage_label))] pub fn derive_stage_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut trait_path = bevy_ecs_path(); @@ -454,7 +454,7 @@ pub fn derive_stage_label(input: TokenStream) -> TokenStream { derive_label(input, &trait_path) } -#[proc_macro_derive(AmbiguitySetLabel)] +#[proc_macro_derive(AmbiguitySetLabel, attributes(ambiguity_set_label))] pub fn derive_ambiguity_set_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut trait_path = bevy_ecs_path(); diff --git a/crates/bevy_macro_utils/Cargo.toml b/crates/bevy_macro_utils/Cargo.toml index c5a19e5c2fb33..579fd42399f5c 100644 --- a/crates/bevy_macro_utils/Cargo.toml +++ b/crates/bevy_macro_utils/Cargo.toml @@ -12,3 +12,4 @@ keywords = ["bevy"] toml = "0.5.8" syn = "1.0" quote = "1.0" +convert_case = { version = "0.5.0", default-features = false } diff --git a/crates/bevy_macro_utils/src/lib.rs b/crates/bevy_macro_utils/src/lib.rs index 589c7c3372af4..0198288312932 100644 --- a/crates/bevy_macro_utils/src/lib.rs +++ b/crates/bevy_macro_utils/src/lib.rs @@ -111,8 +111,31 @@ impl BevyManifest { /// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait /// - `trait_path`: The path [`syn::Path`] to the label trait pub fn derive_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream { + // return true if the variant specified is an `ignore_fields` attribute + fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool { + if attr.path.get_ident().as_ref().unwrap() != &attr_name { + return false; + } + + syn::custom_keyword!(ignore_fields); + attr.parse_args_with(|input: syn::parse::ParseStream| { + let ignore = input.parse::>()?.is_some(); + Ok(ignore) + }) + .unwrap() + } + let ident = input.ident; + use convert_case::{Case, Casing}; + let trait_snake_case = trait_path + .segments + .last() + .unwrap() + .ident + .to_string() + .to_case(Case::Snake); + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause { where_token: Default::default(), @@ -123,22 +146,28 @@ pub fn derive_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStr .push(syn::parse2(quote! { Self: 'static }).unwrap()); let as_str = match input.data { - syn::Data::Struct(d) => match d.fields { - syn::Fields::Unit => { + syn::Data::Struct(d) => { + // Structs must either be fieldless, or explicitly ignore the fields. + let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, &trait_snake_case)); + if matches!(d.fields, syn::Fields::Unit) || ignore_fields { let lit = ident.to_string(); quote! { #lit } + } else { + panic!("Labels cannot contain data, unless explicitly ignored with `#[{trait_snake_case}(ignore_fields)]`"); } - _ => panic!("Labels cannot contain data."), - }, + } syn::Data::Enum(d) => { - let arms = d.variants.iter().map(|v| match v.fields { - syn::Fields::Unit => { + let arms = d.variants.iter().map(|v| { + // Variants must either be fieldless, or explicitly ignore the fields. + let ignore_fields = v.attrs.iter().any(|a| is_ignore(a, &trait_snake_case)); + if matches!(v.fields, syn::Fields::Unit) | ignore_fields { let mut path = syn::Path::from(ident.clone()); path.segments.push(v.ident.clone().into()); let lit = format!("{ident}::{}", v.ident.clone()); - quote! { #path => #lit } + quote! { #path { .. } => #lit } + } else { + panic!("Label variants cannot contain data, unless explicitly ignored with `#[{trait_snake_case}(ignore_fields)]`"); } - _ => panic!("Label variants cannot contain data."), }); quote! { match self { From 2e12912c983c8f685d30652f59cc281e18702b40 Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 18 Jul 2022 07:00:16 -0400 Subject: [PATCH 2/7] add an example for label derives --- crates/bevy_ecs/Cargo.toml | 4 ++ crates/bevy_ecs/examples/derive_label.rs | 47 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 crates/bevy_ecs/examples/derive_label.rs diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 277ecc480ffc9..cdcad37c8648c 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -41,3 +41,7 @@ path = "examples/resources.rs" [[example]] name = "change_detection" path = "examples/change_detection.rs" + +[[example]] +name = "derive_label" +path = "examples/derive_label.rs" diff --git a/crates/bevy_ecs/examples/derive_label.rs b/crates/bevy_ecs/examples/derive_label.rs new file mode 100644 index 0000000000000..b6ef265db22af --- /dev/null +++ b/crates/bevy_ecs/examples/derive_label.rs @@ -0,0 +1,47 @@ +use std::marker::PhantomData; + +use bevy_ecs::prelude::*; + +fn main() { + // Unit labels are always equal. + assert_eq!(UnitLabel.as_label(), UnitLabel.as_label()); + + // Enum labels depend on the variant. + assert_eq!(EnumLabel::One.as_label(), EnumLabel::One.as_label()); + assert_ne!(EnumLabel::One.as_label(), EnumLabel::Two.as_label()); + + // Shockingly, labels annotated with `ignore_fields` ignore their fields. + assert_eq!(WeirdLabel(1).as_label(), WeirdLabel(2).as_label()); + + // Labels of different types are distinct, even if they look similar. + assert_ne!( + GenericLabel::::One.as_label(), + GenericLabel::::One.as_label(), + ); +} + +#[derive(SystemLabel)] +pub struct UnitLabel; + +#[derive(SystemLabel)] +pub enum EnumLabel { + One, + Two, +} + +#[derive(SystemLabel)] +#[system_label(ignore_fields)] +pub struct WeirdLabel(i32); + +#[derive(SystemLabel)] +pub enum GenericLabel { + One, + #[system_label(ignore_fields)] + Two(PhantomData), +} + +// FIXME: this should be a compile_fail test +/*#[derive(SystemLabel)] +pub union Foo { + x: i32, +}*/ From cc895ee7cd18bdab30449b527523649b82d53542 Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 18 Jul 2022 09:48:51 -0400 Subject: [PATCH 3/7] improve diagnostics for Label derive --- crates/bevy_ecs/examples/derive_label.rs | 15 +++++++++++++++ crates/bevy_macro_utils/src/lib.rs | 13 ++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/examples/derive_label.rs b/crates/bevy_ecs/examples/derive_label.rs index b6ef265db22af..040e840b5cb9b 100644 --- a/crates/bevy_ecs/examples/derive_label.rs +++ b/crates/bevy_ecs/examples/derive_label.rs @@ -45,3 +45,18 @@ pub enum GenericLabel { pub union Foo { x: i32, }*/ + +// FIXME: this should be a compile_fail test +/*#[derive(SystemLabel)] +#[system_label(ignore_fields)] +pub enum BadLabel { + One, + Two, +}*/ + +// FIXME: this should be a compile_fail test +/*#[derive(SystemLabel)] +pub struct BadLabel2 { + #[system_label(ignore_fields)] + x: (), +}*/ diff --git a/crates/bevy_macro_utils/src/lib.rs b/crates/bevy_macro_utils/src/lib.rs index 0198288312932..06b4ee973af47 100644 --- a/crates/bevy_macro_utils/src/lib.rs +++ b/crates/bevy_macro_utils/src/lib.rs @@ -145,10 +145,18 @@ pub fn derive_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStr .predicates .push(syn::parse2(quote! { Self: 'static }).unwrap()); + let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, &trait_snake_case)); let as_str = match input.data { syn::Data::Struct(d) => { + // see if the user tried to ignore fields incorrectly + if d.fields + .iter() + .flat_map(|f| &f.attrs) + .any(|a| is_ignore(a, &trait_snake_case)) + { + panic!("`#[{trait_snake_case}(ignore_fields)]` cannot be applied to fields individually: add it to the struct declaration"); + } // Structs must either be fieldless, or explicitly ignore the fields. - let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, &trait_snake_case)); if matches!(d.fields, syn::Fields::Unit) || ignore_fields { let lit = ident.to_string(); quote! { #lit } @@ -157,6 +165,9 @@ pub fn derive_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStr } } syn::Data::Enum(d) => { + if ignore_fields { + panic!("`#[{trait_snake_case}(ignore_fields)]` can only be applied to struct declarations or enum variants"); + } let arms = d.variants.iter().map(|v| { // Variants must either be fieldless, or explicitly ignore the fields. let ignore_fields = v.attrs.iter().any(|a| is_ignore(a, &trait_snake_case)); From ae0769d944476784a03e87bfefd75cc244b8fa6c Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 18 Jul 2022 11:27:26 -0400 Subject: [PATCH 4/7] use spanned errors instead of panics --- crates/bevy_macro_utils/src/lib.rs | 45 ++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/crates/bevy_macro_utils/src/lib.rs b/crates/bevy_macro_utils/src/lib.rs index 06b4ee973af47..56b72331d7151 100644 --- a/crates/bevy_macro_utils/src/lib.rs +++ b/crates/bevy_macro_utils/src/lib.rs @@ -9,8 +9,9 @@ pub use shape::*; pub use symbol::*; use proc_macro::TokenStream; -use quote::quote; +use quote::{quote, quote_spanned}; use std::{env, path::PathBuf}; +use syn::spanned::Spanned; use toml::{map::Map, Value}; pub struct BevyManifest { @@ -125,7 +126,7 @@ pub fn derive_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStr .unwrap() } - let ident = input.ident; + let ident = input.ident.clone(); use convert_case::{Case, Casing}; let trait_snake_case = trait_path @@ -145,28 +146,42 @@ pub fn derive_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStr .predicates .push(syn::parse2(quote! { Self: 'static }).unwrap()); - let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, &trait_snake_case)); let as_str = match input.data { syn::Data::Struct(d) => { // see if the user tried to ignore fields incorrectly - if d.fields + if let Some(attr) = d + .fields .iter() .flat_map(|f| &f.attrs) - .any(|a| is_ignore(a, &trait_snake_case)) + .find(|a| is_ignore(a, &trait_snake_case)) { - panic!("`#[{trait_snake_case}(ignore_fields)]` cannot be applied to fields individually: add it to the struct declaration"); + let err_msg = format!("`#[{trait_snake_case}(ignore_fields)]` cannot be applied to fields individually: add it to the struct declaration"); + return quote_spanned! { + attr.span() => compile_error!(#err_msg); + } + .into(); } // Structs must either be fieldless, or explicitly ignore the fields. + let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, &trait_snake_case)); if matches!(d.fields, syn::Fields::Unit) || ignore_fields { let lit = ident.to_string(); quote! { #lit } } else { - panic!("Labels cannot contain data, unless explicitly ignored with `#[{trait_snake_case}(ignore_fields)]`"); + let err_msg = format!("Labels cannot contain data, unless explicitly ignored with `#[{trait_snake_case}(ignore_fields)]`"); + return quote_spanned! { + d.fields.span() => compile_error!(#err_msg); + } + .into(); } } syn::Data::Enum(d) => { - if ignore_fields { - panic!("`#[{trait_snake_case}(ignore_fields)]` can only be applied to struct declarations or enum variants"); + // check if the user put #[label(ignore_fields)] in the wrong place + if let Some(attr) = input.attrs.iter().find(|a| is_ignore(a, &trait_snake_case)) { + let err_msg = format!("`#[{trait_snake_case}(ignore_fields)]` can only be applied to enum variants or struct declarations"); + return quote_spanned! { + attr.span() => compile_error!(#err_msg); + } + .into(); } let arms = d.variants.iter().map(|v| { // Variants must either be fieldless, or explicitly ignore the fields. @@ -177,7 +192,10 @@ pub fn derive_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStr let lit = format!("{ident}::{}", v.ident.clone()); quote! { #path { .. } => #lit } } else { - panic!("Label variants cannot contain data, unless explicitly ignored with `#[{trait_snake_case}(ignore_fields)]`"); + let err_msg = format!("Label variants cannot contain data, unless explicitly ignored with `#[{trait_snake_case}(ignore_fields)]`"); + quote_spanned! { + v.fields.span() => _ => { compile_error!(#err_msg); } + } } }); quote! { @@ -186,7 +204,12 @@ pub fn derive_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStr } } } - syn::Data::Union(_) => panic!("Unions cannot be used as labels."), + syn::Data::Union(_) => { + return quote_spanned! { + input.span() => compile_error!("Unions cannot be used as labels."); + } + .into(); + } }; (quote! { From dc56e1dc4872f2c9d54f580aa216888f39488f83 Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 18 Jul 2022 11:50:41 -0400 Subject: [PATCH 5/7] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: François --- crates/bevy_ecs/examples/derive_label.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/examples/derive_label.rs b/crates/bevy_ecs/examples/derive_label.rs index 040e840b5cb9b..573b42dc8153d 100644 --- a/crates/bevy_ecs/examples/derive_label.rs +++ b/crates/bevy_ecs/examples/derive_label.rs @@ -10,10 +10,10 @@ fn main() { assert_eq!(EnumLabel::One.as_label(), EnumLabel::One.as_label()); assert_ne!(EnumLabel::One.as_label(), EnumLabel::Two.as_label()); - // Shockingly, labels annotated with `ignore_fields` ignore their fields. + // Labels annotated with `ignore_fields` ignore their fields. assert_eq!(WeirdLabel(1).as_label(), WeirdLabel(2).as_label()); - // Labels of different types are distinct, even if they look similar. + // Labels don't depend only on the variant name but on the full type assert_ne!( GenericLabel::::One.as_label(), GenericLabel::::One.as_label(), From 66d687a889391c110165c5d1a7366ddd93de1f6e Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 18 Jul 2022 15:38:58 -0400 Subject: [PATCH 6/7] add docs to the derive macros --- crates/bevy_derive/src/lib.rs | 4 ++++ crates/bevy_ecs/macros/src/lib.rs | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/crates/bevy_derive/src/lib.rs b/crates/bevy_derive/src/lib.rs index fd7cf9e970cda..638818a30f18b 100644 --- a/crates/bevy_derive/src/lib.rs +++ b/crates/bevy_derive/src/lib.rs @@ -80,6 +80,10 @@ pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream { enum_variant_meta::derive_enum_variant_meta(input) } +/// Generates an impl of the `AppLabel` trait. +/// +/// This works only for unit structs, or enums with only unit variants. +/// You may force a struct or variant to behave as if it were fieldless with `#[app_label(ignore_fields)]`. #[proc_macro_derive(AppLabel, attributes(app_label))] pub fn derive_app_label(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 3ef43e29a4427..d7895e45f76e9 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -434,6 +434,10 @@ pub fn derive_world_query(input: TokenStream) -> TokenStream { derive_world_query_impl(ast) } +/// Generates an impl of the `SystemLabel` trait. +/// +/// This works only for unit structs, or enums with only unit variants. +/// You may force a struct or variant to behave as if it were fieldless with `#[system_label(ignore_fields)]`. #[proc_macro_derive(SystemLabel, attributes(system_label))] pub fn derive_system_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -445,6 +449,10 @@ pub fn derive_system_label(input: TokenStream) -> TokenStream { derive_label(input, &trait_path) } +/// Generates an impl of the `StageLabel` trait. +/// +/// This works only for unit structs, or enums with only unit variants. +/// You may force a struct or variant to behave as if it were fieldless with `#[stage_label(ignore_fields)]`. #[proc_macro_derive(StageLabel, attributes(stage_label))] pub fn derive_stage_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -454,6 +462,10 @@ pub fn derive_stage_label(input: TokenStream) -> TokenStream { derive_label(input, &trait_path) } +/// Generates an impl of the `AmbiguitySetLabel` trait. +/// +/// This works only for unit structs, or enums with only unit variants. +/// You may force a struct or variant to behave as if it were fieldless with `#[ambiguity_set_label(ignore_fields)]`. #[proc_macro_derive(AmbiguitySetLabel, attributes(ambiguity_set_label))] pub fn derive_ambiguity_set_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -465,6 +477,10 @@ pub fn derive_ambiguity_set_label(input: TokenStream) -> TokenStream { derive_label(input, &trait_path) } +/// Generates an impl of the `RunCriteriaLabel` trait. +/// +/// This works only for unit structs, or enums with only unit variants. +/// You may force a struct or variant to behave as if it were fieldless with `#[run_criteria_label(ignore_fields)]`. #[proc_macro_derive(RunCriteriaLabel)] pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); From a633bafd1b0a030f65d798d7aa0ecdfc978d98f0 Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Tue, 19 Jul 2022 01:09:39 -0400 Subject: [PATCH 7/7] remove dependency `convert_case` --- crates/bevy_derive/src/lib.rs | 2 +- crates/bevy_ecs/macros/src/lib.rs | 10 +++++----- crates/bevy_macro_utils/Cargo.toml | 1 - crates/bevy_macro_utils/src/lib.rs | 31 +++++++++++++----------------- 4 files changed, 19 insertions(+), 25 deletions(-) diff --git a/crates/bevy_derive/src/lib.rs b/crates/bevy_derive/src/lib.rs index 638818a30f18b..e2088cfe04ec1 100644 --- a/crates/bevy_derive/src/lib.rs +++ b/crates/bevy_derive/src/lib.rs @@ -89,5 +89,5 @@ pub fn derive_app_label(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); let mut trait_path = BevyManifest::default().get_path("bevy_app"); trait_path.segments.push(format_ident!("AppLabel").into()); - derive_label(input, &trait_path) + derive_label(input, &trait_path, "app_label") } diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index d7895e45f76e9..68023e315ddb0 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -446,7 +446,7 @@ pub fn derive_system_label(input: TokenStream) -> TokenStream { trait_path .segments .push(format_ident!("SystemLabel").into()); - derive_label(input, &trait_path) + derive_label(input, &trait_path, "system_label") } /// Generates an impl of the `StageLabel` trait. @@ -459,7 +459,7 @@ pub fn derive_stage_label(input: TokenStream) -> TokenStream { let mut trait_path = bevy_ecs_path(); trait_path.segments.push(format_ident!("schedule").into()); trait_path.segments.push(format_ident!("StageLabel").into()); - derive_label(input, &trait_path) + derive_label(input, &trait_path, "stage_label") } /// Generates an impl of the `AmbiguitySetLabel` trait. @@ -474,14 +474,14 @@ pub fn derive_ambiguity_set_label(input: TokenStream) -> TokenStream { trait_path .segments .push(format_ident!("AmbiguitySetLabel").into()); - derive_label(input, &trait_path) + derive_label(input, &trait_path, "ambiguity_set_label") } /// Generates an impl of the `RunCriteriaLabel` trait. /// /// This works only for unit structs, or enums with only unit variants. /// You may force a struct or variant to behave as if it were fieldless with `#[run_criteria_label(ignore_fields)]`. -#[proc_macro_derive(RunCriteriaLabel)] +#[proc_macro_derive(RunCriteriaLabel, attributes(run_criteria_label))] pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut trait_path = bevy_ecs_path(); @@ -489,7 +489,7 @@ pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream { trait_path .segments .push(format_ident!("RunCriteriaLabel").into()); - derive_label(input, &trait_path) + derive_label(input, &trait_path, "run_criteria_label") } pub(crate) fn bevy_ecs_path() -> syn::Path { diff --git a/crates/bevy_macro_utils/Cargo.toml b/crates/bevy_macro_utils/Cargo.toml index 579fd42399f5c..c5a19e5c2fb33 100644 --- a/crates/bevy_macro_utils/Cargo.toml +++ b/crates/bevy_macro_utils/Cargo.toml @@ -12,4 +12,3 @@ keywords = ["bevy"] toml = "0.5.8" syn = "1.0" quote = "1.0" -convert_case = { version = "0.5.0", default-features = false } diff --git a/crates/bevy_macro_utils/src/lib.rs b/crates/bevy_macro_utils/src/lib.rs index 56b72331d7151..0af86d052a946 100644 --- a/crates/bevy_macro_utils/src/lib.rs +++ b/crates/bevy_macro_utils/src/lib.rs @@ -111,7 +111,11 @@ impl BevyManifest { /// /// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait /// - `trait_path`: The path [`syn::Path`] to the label trait -pub fn derive_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream { +pub fn derive_label( + input: syn::DeriveInput, + trait_path: &syn::Path, + attr_name: &str, +) -> TokenStream { // return true if the variant specified is an `ignore_fields` attribute fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool { if attr.path.get_ident().as_ref().unwrap() != &attr_name { @@ -128,15 +132,6 @@ pub fn derive_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStr let ident = input.ident.clone(); - use convert_case::{Case, Casing}; - let trait_snake_case = trait_path - .segments - .last() - .unwrap() - .ident - .to_string() - .to_case(Case::Snake); - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause { where_token: Default::default(), @@ -153,21 +148,21 @@ pub fn derive_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStr .fields .iter() .flat_map(|f| &f.attrs) - .find(|a| is_ignore(a, &trait_snake_case)) + .find(|a| is_ignore(a, attr_name)) { - let err_msg = format!("`#[{trait_snake_case}(ignore_fields)]` cannot be applied to fields individually: add it to the struct declaration"); + let err_msg = format!("`#[{attr_name}(ignore_fields)]` cannot be applied to fields individually: add it to the struct declaration"); return quote_spanned! { attr.span() => compile_error!(#err_msg); } .into(); } // Structs must either be fieldless, or explicitly ignore the fields. - let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, &trait_snake_case)); + let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, attr_name)); if matches!(d.fields, syn::Fields::Unit) || ignore_fields { let lit = ident.to_string(); quote! { #lit } } else { - let err_msg = format!("Labels cannot contain data, unless explicitly ignored with `#[{trait_snake_case}(ignore_fields)]`"); + let err_msg = format!("Labels cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`"); return quote_spanned! { d.fields.span() => compile_error!(#err_msg); } @@ -176,8 +171,8 @@ pub fn derive_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStr } syn::Data::Enum(d) => { // check if the user put #[label(ignore_fields)] in the wrong place - if let Some(attr) = input.attrs.iter().find(|a| is_ignore(a, &trait_snake_case)) { - let err_msg = format!("`#[{trait_snake_case}(ignore_fields)]` can only be applied to enum variants or struct declarations"); + if let Some(attr) = input.attrs.iter().find(|a| is_ignore(a, attr_name)) { + let err_msg = format!("`#[{attr_name}(ignore_fields)]` can only be applied to enum variants or struct declarations"); return quote_spanned! { attr.span() => compile_error!(#err_msg); } @@ -185,14 +180,14 @@ pub fn derive_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStr } let arms = d.variants.iter().map(|v| { // Variants must either be fieldless, or explicitly ignore the fields. - let ignore_fields = v.attrs.iter().any(|a| is_ignore(a, &trait_snake_case)); + let ignore_fields = v.attrs.iter().any(|a| is_ignore(a, attr_name)); if matches!(v.fields, syn::Fields::Unit) | ignore_fields { let mut path = syn::Path::from(ident.clone()); path.segments.push(v.ident.clone().into()); let lit = format!("{ident}::{}", v.ident.clone()); quote! { #path { .. } => #lit } } else { - let err_msg = format!("Label variants cannot contain data, unless explicitly ignored with `#[{trait_snake_case}(ignore_fields)]`"); + let err_msg = format!("Label variants cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`"); quote_spanned! { v.fields.span() => _ => { compile_error!(#err_msg); } }