dioxus_stores_macro/lib.rs
1use proc_macro::TokenStream;
2use syn::{parse_macro_input, DeriveInput, ItemImpl};
3
4use crate::extend::ExtendArgs;
5
6mod derive;
7mod extend;
8
9/// # `derive(Store)`
10///
11/// The `Store` macro is used to create an extension trait for stores that makes it possible to access the fields or variants
12/// of an item as stores.
13///
14/// ## Expansion
15///
16/// The macro expands to two different items:
17/// - An extension trait which is implemented for `Store<YourType, W>` with methods to access fields and variants for your type.
18/// - A transposed version of your type which contains the fields or variants as stores.
19///
20/// ### Structs
21///
22/// 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:
23///
24/// ```rust, no_run
25/// use dioxus::prelude::*;
26/// use dioxus_stores::*;
27///
28/// #[derive(Store)]
29/// struct TodoItem {
30/// checked: bool,
31/// contents: String,
32/// }
33///
34/// let store = use_store(|| TodoItem {
35/// checked: false,
36/// contents: "Learn about stores".to_string(),
37/// });
38///
39/// // The store macro creates an extension trait with methods for each field
40/// // that returns a store scoped to that field.
41/// let checked: Store<bool, _> = store.checked();
42/// let contents: Store<String, _> = store.contents();
43///
44/// // It also generates a `transpose` method returns a variant of your structure
45/// // with stores wrapping each of your data types. This can be very useful when destructuring
46/// // or matching your type
47/// let TodoItemStoreTransposed { checked, contents } = store.transpose();
48/// let checked: bool = checked();
49/// let contents: String = contents();
50/// ```
51///
52///
53/// ### Enums
54///
55/// 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.
56///
57/// ```rust, no_run
58/// use dioxus::prelude::*;
59/// use dioxus_stores::*;
60///
61/// #[derive(Store, PartialEq, Clone, Debug)]
62/// enum Enum {
63/// Foo(String),
64/// Bar { foo: i32, bar: String },
65/// }
66///
67/// let store = use_store(|| Enum::Foo("Hello".to_string()));
68/// // The store macro creates an extension trait with methods for each variant to check
69/// // if the store is that variant.
70/// let foo: bool = store.is_foo();
71/// let bar: bool = store.is_bar();
72///
73/// // If there is only one field in the variant, it also generates a method to try
74/// // to downcast the store to that variant.
75/// let foo: Option<Store<String, _>> = store.foo();
76/// if let Some(foo) = foo {
77/// println!("Foo: {foo}");
78/// }
79///
80/// // It also generates a `transpose` method that returns a variant of your enum where all
81/// // the fields are stores. You can use this to match your enum
82/// let transposed = store.transpose();
83/// use EnumStoreTransposed::*;
84/// match transposed {
85/// EnumStoreTransposed::Foo(foo) => println!("Foo: {foo}"),
86/// EnumStoreTransposed::Bar { foo, bar } => {
87/// let foo: i32 = foo();
88/// let bar: String = bar();
89/// println!("Bar: foo = {foo}, bar = {bar}");
90/// }
91/// }
92/// ```
93#[proc_macro_derive(Store)]
94pub fn derive_store(input: TokenStream) -> TokenStream {
95 // Parse the input tokens into a syntax tree
96 let input = parse_macro_input!(input as DeriveInput);
97
98 let expanded = match derive::derive_store(input) {
99 Ok(tokens) => tokens,
100 Err(err) => {
101 // If there was an error, return it as a compile error
102 return err.to_compile_error().into();
103 }
104 };
105
106 // Hand the output tokens back to the compiler
107 TokenStream::from(expanded)
108}
109
110/// # `#[store]`
111///
112/// The `store` attribute macro is used to create an extension trait for store implementations. The extension traits lets you add
113/// methods to the store even though the type is not defined in your crate.
114///
115/// ## Arguments
116///
117/// - `pub`: Makes the generated extension trait public. If not provided, the trait will be private.
118/// - `name = YourExtensionName`: The name of the extension trait. If not provided, it will be generated based on the type name.
119///
120/// ## Bounds
121///
122/// The generated extension trait will have bounds on the lens generic parameter to ensure it implements `Readable` or `Writable` as needed.
123///
124/// - If a method accepts `&self`, the lens will require `Readable` which lets you read the value of the store.
125/// - If a method accepts `&mut self`, the lens will require `Writable` which lets you change the value of the store.
126///
127/// ## Example
128///
129/// ```rust, no_run
130/// use dioxus::prelude::*;
131/// use dioxus_stores::*;
132///
133/// #[derive(Store)]
134/// struct TodoItem {
135/// checked: bool,
136/// contents: String,
137/// }
138///
139/// // You can use the store attribute macro to add methods to your stores
140/// #[store]
141/// impl<Lens> Store<TodoItem, Lens> {
142/// // Since this method takes &mut self, the lens will require Writable automatically. It cannot be used
143/// // with ReadStore<TodoItem>
144/// fn toggle_checked(&mut self) {
145/// self.checked().toggle();
146/// }
147///
148/// // Since this method takes &self, the lens will require Readable automatically
149/// fn checked_contents(&self) -> Option<String> {
150/// self.checked().cloned().then(|| self.contents().to_string())
151/// }
152/// }
153///
154/// let mut store = use_store(|| TodoItem {
155/// checked: false,
156/// contents: "Learn about stores".to_string(),
157/// });
158///
159/// // You can use the methods defined in the extension trait
160/// store.toggle_checked();
161/// let contents: Option<String> = store.checked_contents();
162/// ```
163#[proc_macro_attribute]
164pub fn store(args: TokenStream, input: TokenStream) -> TokenStream {
165 // Parse the input tokens into a syntax tree
166 let args = parse_macro_input!(args as ExtendArgs);
167 let input = parse_macro_input!(input as ItemImpl);
168
169 let expanded = match extend::extend_store(args, input) {
170 Ok(tokens) => tokens,
171 Err(err) => {
172 // If there was an error, return it as a compile error
173 return err.to_compile_error().into();
174 }
175 };
176
177 // Hand the output tokens back to the compiler
178 TokenStream::from(expanded)
179}