use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput, ItemImpl}; use crate::extend::ExtendArgs; mod derive; mod extend; /// # `derive(Store)` /// /// The `Store` macro is used to create an extension trait for stores that makes it possible to access the fields or variants /// of an item as stores. /// /// ## Expansion /// /// The macro expands to two different items: /// - An extension trait which is implemented for `Store` with methods to access fields and variants for your type. /// - A transposed version of your type which contains the fields or variants as stores. /// /// ### Structs /// /// For structs, the store macro generates methods for each field that returns a store scoped to that field and a `transpose` method that returns a struct with all fields as stores: /// /// ```rust, no_run /// use dioxus::prelude::*; /// use dioxus_stores::*; /// /// #[derive(Store)] /// struct TodoItem { /// checked: bool, /// contents: String, /// } /// /// let store = use_store(|| TodoItem { /// checked: false, /// contents: "Learn about stores".to_string(), /// }); /// /// // The store macro creates an extension trait with methods for each field /// // that returns a store scoped to that field. /// let checked: Store = store.checked(); /// let contents: Store = store.contents(); /// /// // It also generates a `transpose` method returns a variant of your structure /// // with stores wrapping each of your data types. This can be very useful when destructuring /// // or matching your type /// let TodoItemStoreTransposed { checked, contents } = store.transpose(); /// let checked: bool = checked(); /// let contents: String = contents(); /// ``` /// /// /// ### Enums /// /// For enums, the store macro generates methods for each variant that checks if the store is that variant. It also generates a `transpose` method that returns an enum with all fields as stores. /// /// ```rust, no_run /// use dioxus::prelude::*; /// use dioxus_stores::*; /// /// #[derive(Store, PartialEq, Clone, Debug)] /// enum Enum { /// Foo(String), /// Bar { foo: i32, bar: String }, /// } /// /// let store = use_store(|| Enum::Foo("Hello".to_string())); /// // The store macro creates an extension trait with methods for each variant to check /// // if the store is that variant. /// let foo: bool = store.is_foo(); /// let bar: bool = store.is_bar(); /// /// // If there is only one field in the variant, it also generates a method to try /// // to downcast the store to that variant. /// let foo: Option> = store.foo(); /// if let Some(foo) = foo { /// println!("Foo: {foo}"); /// } /// /// // It also generates a `transpose` method that returns a variant of your enum where all /// // the fields are stores. You can use this to match your enum /// let transposed = store.transpose(); /// use EnumStoreTransposed::*; /// match transposed { /// EnumStoreTransposed::Foo(foo) => println!("Foo: {foo}"), /// EnumStoreTransposed::Bar { foo, bar } => { /// let foo: i32 = foo(); /// let bar: String = bar(); /// println!("Bar: foo = {foo}, bar = {bar}"); /// } /// } /// ``` #[proc_macro_derive(Store)] pub fn derive_store(input: TokenStream) -> TokenStream { // Parse the input tokens into a syntax tree let input = parse_macro_input!(input as DeriveInput); let expanded = match derive::derive_store(input) { Ok(tokens) => tokens, Err(err) => { // If there was an error, return it as a compile error return err.to_compile_error().into(); } }; // Hand the output tokens back to the compiler TokenStream::from(expanded) } /// # `#[store]` /// /// The `store` attribute macro is used to create an extension trait for store implementations. The extension traits lets you add /// methods to the store even though the type is not defined in your crate. /// /// ## Arguments /// /// - `pub`: Makes the generated extension trait public. If not provided, the trait will be private. /// - `name = YourExtensionName`: The name of the extension trait. If not provided, it will be generated based on the type name. /// /// ## Bounds /// /// The generated extension trait will have bounds on the lens generic parameter to ensure it implements `Readable` or `Writable` as needed. /// /// - If a method accepts `&self`, the lens will require `Readable` which lets you read the value of the store. /// - If a method accepts `&mut self`, the lens will require `Writable` which lets you change the value of the store. /// /// ## Example /// /// ```rust, no_run /// use dioxus::prelude::*; /// use dioxus_stores::*; /// /// #[derive(Store)] /// struct TodoItem { /// checked: bool, /// contents: String, /// } /// /// // You can use the store attribute macro to add methods to your stores /// #[store] /// impl Store { /// // Since this method takes &mut self, the lens will require Writable automatically. It cannot be used /// // with ReadStore /// fn toggle_checked(&mut self) { /// self.checked().toggle(); /// } /// /// // Since this method takes &self, the lens will require Readable automatically /// fn checked_contents(&self) -> Option { /// self.checked().cloned().then(|| self.contents().to_string()) /// } /// } /// /// let mut store = use_store(|| TodoItem { /// checked: false, /// contents: "Learn about stores".to_string(), /// }); /// /// // You can use the methods defined in the extension trait /// store.toggle_checked(); /// let contents: Option = store.checked_contents(); /// ``` #[proc_macro_attribute] pub fn store(args: TokenStream, input: TokenStream) -> TokenStream { // Parse the input tokens into a syntax tree let args = parse_macro_input!(args as ExtendArgs); let input = parse_macro_input!(input as ItemImpl); let expanded = match extend::extend_store(args, input) { Ok(tokens) => tokens, Err(err) => { // If there was an error, return it as a compile error return err.to_compile_error().into(); } }; // Hand the output tokens back to the compiler TokenStream::from(expanded) }