diff --git a/immediate_stats_macros/src/lib.rs b/immediate_stats_macros/src/lib.rs index f5baa51..88072aa 100644 --- a/immediate_stats_macros/src/lib.rs +++ b/immediate_stats_macros/src/lib.rs @@ -30,22 +30,22 @@ pub fn stat_container_derive(item: proc_macro::TokenStream) -> proc_macro::Token } fn stat_container_struct(s: DataStruct) -> TokenStream { - match get_names_from_fields(s.fields) { - FieldAccess::Ident(names) => { + match get_members_from_fields(s.fields) { + MemberVec::Named(names) => { quote! { fn reset_modifiers(&mut self) { #(self.#names.reset_modifiers();)* } } } - FieldAccess::Index(nums) => { + MemberVec::Unnamed(nums) => { quote! { fn reset_modifiers(&mut self) { #(self.#nums.reset_modifiers();)* } } } - FieldAccess::None => { + MemberVec::None => { emit_call_site_warning!( "Unused derive. Consider adding `#[stat]` to a field that implements `StatContainer`." ); @@ -60,15 +60,15 @@ fn stat_container_enum(e: DataEnum) -> TokenStream { for variant in e.variants { let ident = variant.ident; - match get_names_from_fields(variant.fields.clone()) { - FieldAccess::Ident(names) => { + match get_members_from_fields(variant.fields.clone()) { + MemberVec::Named(names) => { cases.push(quote! { Self::#ident { #(#names,)* .. } => { #(#names.reset_modifiers();)* }, }); } - FieldAccess::Index(nums) => { + MemberVec::Unnamed(nums) => { let mut variables = Vec::new(); for index in 0..variant.fields.len() { @@ -94,7 +94,7 @@ fn stat_container_enum(e: DataEnum) -> TokenStream { }, }); } - FieldAccess::None => {} + MemberVec::None => {} } } @@ -115,14 +115,14 @@ fn stat_container_enum(e: DataEnum) -> TokenStream { } /// The ways to identify fields. -enum FieldAccess { - Ident(Vec), - Index(Vec), +enum MemberVec { + Named(Vec), + Unnamed(Vec), None, } /// Returns a list of all fields that either have type `Stat` or are tagged with `#[stat]`. -fn get_names_from_fields>(fields: T) -> FieldAccess { +fn get_members_from_fields>(fields: T) -> MemberVec { let mut names = Vec::new(); let mut nums = Vec::new(); @@ -136,14 +136,14 @@ fn get_names_from_fields>(fields: T) -> FieldAcces for attr in &field.attrs { let path = attr.meta.path(); - let Some(ident) = path.get_ident() else { + let Some(attr_ident) = path.get_ident() else { continue; }; - if ident.to_string() == "stat" { + if attr_ident.to_string() == "stat" { if is_stat { emit_warning!( - ident, + attr_ident, "Unnecessary `stat` attribute. Fields of type `Stat` are automatically included." ); } @@ -169,10 +169,10 @@ fn get_names_from_fields>(fields: T) -> FieldAcces assert!(names.is_empty() | nums.is_empty()); if names.is_empty() { - FieldAccess::Index(nums) + MemberVec::Unnamed(nums) } else if nums.is_empty() { - FieldAccess::Ident(names) + MemberVec::Named(names) } else { - FieldAccess::None + MemberVec::None } } -- 2.51.2 From ae1734a75dba7f19b477c4cf92644834d621fee6 Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Wed, 26 Mar 2025 11:01:27 -0700 Subject: [PATCH 2/4] Added `#[stat_ignore]` attribute. --- immediate_stats/src/lib.rs | 28 ++++++++++++++++++++++++++++ immediate_stats_macros/src/lib.rs | 23 ++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/immediate_stats/src/lib.rs b/immediate_stats/src/lib.rs index 82d9127..6ab2394 100644 --- a/immediate_stats/src/lib.rs +++ b/immediate_stats/src/lib.rs @@ -97,6 +97,34 @@ mod tests { } } + #[derive(StatContainer, PartialEq, Debug)] + struct IgnoreTest { + #[stat_ignore] + ignored: Stat, + reset: Stat, + } + + #[test] + fn reset_ignored() { + for base in 0..10 { + let stat = Stat { + base, + bonus: 3, + multiplier: 1.5, + }; + + let mut health = IgnoreTest { + ignored: stat, + reset: stat, + }; + + health.reset_modifiers(); + + assert_eq!(health.ignored, stat); + assert_eq!(health.reset, Stat::new(base)); + } + } + #[derive(StatContainer, PartialEq, Debug)] enum EnumStat { Named { diff --git a/immediate_stats_macros/src/lib.rs b/immediate_stats_macros/src/lib.rs index 88072aa..d0ab65d 100644 --- a/immediate_stats_macros/src/lib.rs +++ b/immediate_stats_macros/src/lib.rs @@ -5,7 +5,7 @@ use proc_macro2::{Span, TokenStream}; use quote::{ToTokens, quote}; use syn::{Data, DataEnum, DataStruct, DeriveInput, Field, Ident, Index}; -#[proc_macro_derive(StatContainer, attributes(stat))] +#[proc_macro_derive(StatContainer, attributes(stat, stat_ignore))] #[proc_macro_error] pub fn stat_container_derive(item: proc_macro::TokenStream) -> proc_macro::TokenStream { let tree: DeriveInput = syn::parse(item).expect("A valid TokenStream"); @@ -134,6 +134,8 @@ fn get_members_from_fields>(fields: T) -> MemberVe is_stat = true; } + let mut explicit_stat: Option = None; + for attr in &field.attrs { let path = attr.meta.path(); let Some(attr_ident) = path.get_ident() else { @@ -149,6 +151,25 @@ fn get_members_from_fields>(fields: T) -> MemberVe } is_stat = true; + explicit_stat = Some(attr_ident.clone()); + } + } + + for attr in &field.attrs { + let path = attr.meta.path(); + let Some(attr_ident) = path.get_ident() else { + continue; + }; + + if attr_ident.to_string() == "stat_ignore" { + if let Some(explicit_ident) = &explicit_stat { + emit_warning!( + explicit_ident, + "`stat` attribute is overruled by `stat_ignore` attribute." + ); + } + + is_stat = false; } } -- 2.51.2 From ee2e945cc3b4b1f1676c9bfc1e2a19444363723f Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Wed, 26 Mar 2025 11:06:40 -0700 Subject: [PATCH 3/4] Test renaming. --- immediate_stats/src/bevy.rs | 4 ++-- immediate_stats/src/lib.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/immediate_stats/src/bevy.rs b/immediate_stats/src/bevy.rs index 0d9b69a..f1118d9 100644 --- a/immediate_stats/src/bevy.rs +++ b/immediate_stats/src/bevy.rs @@ -41,7 +41,7 @@ mod tests { struct Health(Stat); #[test] - fn reset() { + fn reset_component() { let mut world = World::new(); let system = world.register_system(reset_modifiers::); @@ -62,7 +62,7 @@ mod tests { } #[test] - fn pause_reset() { + fn pause_component() { let mut world = World::new(); let system = world.register_system(reset_modifiers::); diff --git a/immediate_stats/src/lib.rs b/immediate_stats/src/lib.rs index 6ab2394..39561bd 100644 --- a/immediate_stats/src/lib.rs +++ b/immediate_stats/src/lib.rs @@ -52,7 +52,7 @@ mod tests { struct MaxHealth(Stat, bool); #[test] - fn reset_tuple() { + fn reset_tuple_struct() { for base in 0..10 { let mut max_health = MaxHealth( Stat { @@ -98,7 +98,7 @@ mod tests { } #[derive(StatContainer, PartialEq, Debug)] - struct IgnoreTest { + struct PartialReset { #[stat_ignore] ignored: Stat, reset: Stat, @@ -113,15 +113,15 @@ mod tests { multiplier: 1.5, }; - let mut health = IgnoreTest { + let mut partial = PartialReset { ignored: stat, reset: stat, }; - health.reset_modifiers(); + partial.reset_modifiers(); - assert_eq!(health.ignored, stat); - assert_eq!(health.reset, Stat::new(base)); + assert_eq!(partial.ignored, stat); + assert_eq!(partial.reset, Stat::new(base)); } } -- 2.51.2 From ad22d301f647ee8650ce1cead081abba0037b5ca Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Wed, 26 Mar 2025 11:32:13 -0700 Subject: [PATCH 4/4] Cleanup attribute detection code. --- immediate_stats_macros/src/lib.rs | 62 +++++++++++++++---------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/immediate_stats_macros/src/lib.rs b/immediate_stats_macros/src/lib.rs index d0ab65d..2f0f342 100644 --- a/immediate_stats_macros/src/lib.rs +++ b/immediate_stats_macros/src/lib.rs @@ -8,7 +8,7 @@ use syn::{Data, DataEnum, DataStruct, DeriveInput, Field, Ident, Index}; #[proc_macro_derive(StatContainer, attributes(stat, stat_ignore))] #[proc_macro_error] pub fn stat_container_derive(item: proc_macro::TokenStream) -> proc_macro::TokenStream { - let tree: DeriveInput = syn::parse(item).expect("A valid TokenStream"); + let tree: DeriveInput = syn::parse(item).expect("TokenStream must be valid."); let struct_name = &tree.ident; @@ -16,7 +16,7 @@ pub fn stat_container_derive(item: proc_macro::TokenStream) -> proc_macro::Token Data::Struct(s) => stat_container_struct(s), Data::Enum(e) => stat_container_enum(e), Data::Union(_) => { - emit_call_site_error!("This trait cannot be derived for unions"); + emit_call_site_error!("This trait cannot be derived for unions."); return proc_macro::TokenStream::new(); } }; @@ -122,6 +122,7 @@ enum MemberVec { } /// Returns a list of all fields that either have type `Stat` or are tagged with `#[stat]`. +/// If they are tagged with `#[stat_ignore]`, they are removed from the list. fn get_members_from_fields>(fields: T) -> MemberVec { let mut names = Vec::new(); let mut nums = Vec::new(); @@ -130,47 +131,44 @@ fn get_members_from_fields>(fields: T) -> MemberVe // Check if the field is a stat. let mut is_stat = false; + // Check if type is `Stat`. if field.ty.to_token_stream().to_string() == "Stat" { is_stat = true; } + // Store the `#[stat]` ident. Used for warning when overridden by `#[stat_ignore]`. let mut explicit_stat: Option = None; - for attr in &field.attrs { - let path = attr.meta.path(); - let Some(attr_ident) = path.get_ident() else { - continue; - }; - - if attr_ident.to_string() == "stat" { - if is_stat { - emit_warning!( - attr_ident, - "Unnecessary `stat` attribute. Fields of type `Stat` are automatically included." - ); - } + // Iterator over all ident attributes. + let attr_ident_iter = field.attrs.iter().filter_map(|x| x.meta.path().get_ident()); - is_stat = true; - explicit_stat = Some(attr_ident.clone()); + // Check for `#[stat]` attribute. + if let Some(attr_ident) = attr_ident_iter.clone().find(|x| x.to_string() == "stat") { + if is_stat { + emit_warning!( + attr_ident, + "Unnecessary `stat` attribute. Fields of type `Stat` are automatically included." + ); } - } - for attr in &field.attrs { - let path = attr.meta.path(); - let Some(attr_ident) = path.get_ident() else { - continue; - }; - - if attr_ident.to_string() == "stat_ignore" { - if let Some(explicit_ident) = &explicit_stat { - emit_warning!( - explicit_ident, - "`stat` attribute is overruled by `stat_ignore` attribute." - ); - } + is_stat = true; + explicit_stat = Some(attr_ident.clone()); + } - is_stat = false; + // Check for `#[stat_ignore]` attribute. + if attr_ident_iter + .clone() + .find(|x| x.to_string() == "stat_ignore") + .is_some() + { + if let Some(explicit_ident) = &explicit_stat { + emit_warning!( + explicit_ident, + "`stat` attribute is overruled by `stat_ignore` attribute." + ); } + + is_stat = false; } if !is_stat {