From 514ca860ad24ac306f04b378b98d7a3a1b52bd39 Mon Sep 17 00:00:00 2001 From: Maxim Deloof <20443795+mdeloof@users.noreply.github.com> Date: Wed, 8 Jan 2025 01:25:40 +0100 Subject: [PATCH] Add option for default value to attribute --- examples/macro/barrier/src/main.rs | 4 ++- macro/src/analyze.rs | 57 +++++++++++++++++++++++++----- macro/src/lower.rs | 13 +++++-- statig/tests/default.rs | 5 ++- 4 files changed, 66 insertions(+), 13 deletions(-) diff --git a/examples/macro/barrier/src/main.rs b/examples/macro/barrier/src/main.rs index bcf6d15..39a04c9 100644 --- a/examples/macro/barrier/src/main.rs +++ b/examples/macro/barrier/src/main.rs @@ -13,7 +13,7 @@ enum Event { impl Foo { #[state(superstate = "waiting_for_initialization")] fn initializing( - #[default] a: &mut bool, + #[default = "true"] a: &mut bool, #[default] b: &mut bool, #[default] c: &mut bool, event: &Event, @@ -51,6 +51,8 @@ impl Foo { fn main() { let mut state_machine = Foo::default().uninitialized_state_machine().init(); + dbg!(state_machine.state()); + state_machine.handle(&Event::A); state_machine.handle(&Event::B); state_machine.handle(&Event::C); diff --git a/macro/src/analyze.rs b/macro/src/analyze.rs index 0c97add..f104169 100644 --- a/macro/src/analyze.rs +++ b/macro/src/analyze.rs @@ -3,9 +3,9 @@ use std::collections::HashMap; use proc_macro_error::abort; use syn::parse::Parser; use syn::{ - parse_quote, Attribute, AttributeArgs, ExprCall, Field, FnArg, Generics, Ident, ImplItem, - ImplItemMethod, ItemImpl, Lit, Meta, MetaList, NestedMeta, Pat, PatType, Path, Receiver, Type, - Visibility, + parse_quote, Attribute, AttributeArgs, Expr, Field, FnArg, Generics, Ident, ImplItem, + ImplItemMethod, ItemImpl, Lit, Meta, MetaList, NestedMeta, Pat, PatIdent, PatType, Path, + Receiver, Type, Visibility, }; /// Model of the state machine. @@ -27,7 +27,7 @@ pub struct Model { #[cfg_attr(test, derive(Debug, Eq, PartialEq))] pub struct StateMachine { /// The inital state of the state machine. - pub initial_state: ExprCall, + pub initial_state: Expr, /// The type on which the state machine is implemented. pub shared_storage_type: Type, /// The path of the shared storage. @@ -72,7 +72,7 @@ pub struct State { /// Local storage, pub local_storage: Vec, /// Local storage default. - pub local_storage_default: Vec, + pub local_storage_default: Vec, /// Inputs required by the state handler. pub inputs: Vec, /// Optional receiver input for the state handler (e.g. `&mut self`). @@ -125,6 +125,22 @@ pub struct Action { pub is_async: bool, } +/// Information regarding a local storage default. +#[cfg_attr(test, derive(Debug, Eq, PartialEq))] +pub enum LocalStorageDefault { + Empty { ident: Ident }, + Value { ident: Ident, value: Expr }, +} + +impl LocalStorageDefault { + pub(crate) fn ident(&self) -> &Ident { + match self { + LocalStorageDefault::Empty { ident } => ident, + LocalStorageDefault::Value { ident, .. } => ident, + } + } +} + /// Analyze the impl block and create a model. pub fn analyze(attribute_args: AttributeArgs, mut item_impl: ItemImpl) -> Model { let state_machine = analyze_state_machine(&attribute_args, &item_impl); @@ -178,7 +194,7 @@ pub fn analyze_state_machine(attribute_args: &AttributeArgs, item_impl: &ItemImp let shared_storage_generics = item_impl.generics.clone(); let shared_storage_path = get_shared_storage_path(&shared_storage_type); - let mut initial_state: Option = None; + let mut initial_state: Option = None; let mut state_ident = parse_quote!(State); let mut state_derives = Vec::new(); @@ -418,8 +434,9 @@ pub fn analyze_state(method: &mut ImplItemMethod, state_machine: &StateMachine) .iter() .position(|attr| attr.path.is_ident("default")) { - pat_type.attrs.swap_remove(index); - local_storage_default.push(input.ident.clone()); + let attr = pat_type.attrs.swap_remove(index); + let default = analyze_local_storage_default(input, &attr); + local_storage_default.push(default); } state_inputs.push(pat_type.clone()); @@ -611,6 +628,30 @@ pub fn analyze_action(method: &ImplItemMethod) -> Action { } } +fn analyze_local_storage_default(input: &PatIdent, attribute: &Attribute) -> LocalStorageDefault { + let Ok(meta) = attribute.parse_meta() else { + abort!(attribute, "attribute must use meta syntax") + }; + match meta { + Meta::Path(_) => LocalStorageDefault::Empty { + ident: input.ident.clone(), + }, + Meta::NameValue(name_value) => { + let Lit::Str(literal) = name_value.lit else { + abort!(name_value.lit, "must be a string literal") + }; + let Ok(expr) = literal.parse() else { + abort!(literal, "must be an expression") + }; + LocalStorageDefault::Value { + ident: input.ident.clone(), + value: expr, + } + } + _ => abort!(attribute, "wrong attribute format"), + } +} + /// Parse the attributes as a meta item. pub fn get_meta(attrs: &[Attribute], name: &str) -> Vec { attrs diff --git a/macro/src/lower.rs b/macro/src/lower.rs index 17d5eb4..16bc271 100644 --- a/macro/src/lower.rs +++ b/macro/src/lower.rs @@ -475,12 +475,19 @@ pub fn lower_state(state: &analyze::State, state_machine: &analyze::StateMachine // Check if variant field should use default value. for field in &variant_fields { let field_name = &field.ident; - if state + if let Some(default) = state .local_storage_default .iter() - .any(|default| field.ident.as_ref().unwrap() == default) + .find(|default| field.ident.as_ref().unwrap() == default.ident()) { - field_values.push(parse_quote!(#field_name: core::default::Default::default())) + match default { + analyze::LocalStorageDefault::Empty { ident } => { + field_values.push(parse_quote!(#ident: core::default::Default::default())) + } + analyze::LocalStorageDefault::Value { ident, value } => { + field_values.push(parse_quote!(#ident: #value)) + } + } } else { constructor_args.push(field.clone()); field_values.push(parse_quote!(#field_name)); diff --git a/statig/tests/default.rs b/statig/tests/default.rs index 8f34a25..4460e16 100644 --- a/statig/tests/default.rs +++ b/statig/tests/default.rs @@ -7,7 +7,10 @@ mod tests { #[state_machine(initial = "State::bar()")] impl Foo { #[state] - fn bar(#[default] _local: &mut usize) -> Response { + fn bar( + #[default] _local: &mut usize, + #[default = "100"] _local_2: &mut usize, + ) -> Response { Handled } }