Skip to content

Commit 1405fca

Browse files
committed
feat(macros): refactor the signal! macro to return a &'static Rc<Signal<T>> type, remove the ref_signal! macro, and update the documentation, sample code, and test cases.
1 parent 7a9d3fc commit 1405fca

File tree

6 files changed

+70
-171
lines changed

6 files changed

+70
-171
lines changed

cache/src/effect.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ impl Effect {
166166
/// ```
167167
/// use std::{cell::Cell, rc::Rc};
168168
/// use reactive_cache::Effect;
169-
/// use reactive_macros::{ref_signal, signal};
169+
/// use reactive_macros::signal;
170170
///
171171
/// signal!(static mut FLAG: bool = true;);
172172
/// signal!(static mut COUNTER: i32 = 10;);
@@ -177,29 +177,29 @@ impl Effect {
177177
/// // Effect closure has a conditional branch
178178
/// let effect = Effect::new_with_deps(
179179
/// move || {
180-
/// match *FLAG_get() {
180+
/// match *FLAG().get() {
181181
/// true => {}
182182
/// false => {
183-
/// r_clone.set(*COUNTER_get());
183+
/// r_clone.set(*COUNTER().get());
184184
/// }
185185
/// }
186186
/// },
187187
/// // Explicitly declare both `FLAG` and `COUNTER` as dependencies
188188
/// move || {
189-
/// FLAG();
190-
/// COUNTER();
189+
/// FLAG().get();
190+
/// COUNTER().get();
191191
/// },
192192
/// );
193193
///
194194
/// assert_eq!(result.get(), 0); // runs with FLAG = true
195195
///
196196
/// // Changing `FLAG` to false will trigger the effect
197-
/// FLAG_set(false);
197+
/// FLAG().set(false);
198198
/// assert_eq!(result.get(), 10);
199199
///
200200
/// // Changing `COUNTER` still triggers the effect, even though
201201
/// // `FLAG` was true on the first run.
202-
/// COUNTER_set(20);
202+
/// COUNTER().set(20);
203203
/// assert_eq!(result.get(), 20);
204204
/// ```
205205
pub fn new_with_deps(f: impl Fn() + 'static, deps: impl Fn()) -> Rc<Effect> {

cache/src/macros.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
/// ```rust
2828
/// use std::{cell::Cell, rc::Rc};
2929
/// use reactive_cache::effect;
30-
/// use reactive_macros::{ref_signal, signal};
30+
/// use reactive_macros::signal;
3131
///
3232
/// signal!(static mut A: i32 = 1;);
3333
///
@@ -37,7 +37,7 @@
3737
///
3838
/// // `effect!(f)` form
3939
/// let e = effect!(move || {
40-
/// let _ = A(); // reading the signal
40+
/// let _ = A().get(); // reading the signal
4141
/// counter_clone.set(counter_clone.get() + 1); // increment effect counter
4242
/// });
4343
///
@@ -47,11 +47,11 @@
4747
/// assert_eq!(counter.get(), 1);
4848
///
4949
/// // Changing A triggers the effect again
50-
/// assert!(A_set(10));
50+
/// assert!(A().set(10));
5151
/// assert_eq!(counter.get(), 2);
5252
///
5353
/// // Setting the same value does NOT trigger the effect
54-
/// assert!(!A_set(10));
54+
/// assert!(!A().set(10));
5555
/// assert_eq!(counter.get(), 2);
5656
///
5757
/// // `effect!(f, deps)` form

macros/src/lib.rs

Lines changed: 26 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,34 @@ use proc_macro::TokenStream;
22
use quote::{format_ident, quote};
33
use syn::{Ident, ItemFn, ItemStatic, ReturnType, parse_macro_input};
44

5-
/// Wraps a `static mut` variable as a reactive signal (similar to a property)
6-
/// with getter and setter functions.
5+
/// Wraps a `static mut` variable as a reactive global signal.
76
///
8-
/// The `ref_signal!` macro transforms a `static mut` variable into a `reactive_cache::Signal`,
9-
/// and automatically generates:
10-
/// 1. A `_get()` function that returns a reference to the value, allowing read access.
11-
/// - This reference behaves like a normal immutable reference for most purposes.
12-
/// 2. A `_set(value)` function to write the value (returns `true` if changed).
13-
///
14-
/// Unlike `signal!`, `ref_signal!` does **not** generate a same-named function that directly returns the value.
7+
/// The `signal!` macro transforms a `static mut` variable into a `reactive_cache::Signal`,
8+
/// and generates a **function with the same name as the variable** that returns a
9+
/// `&'static Rc<Signal<T>>`. You can then call `.get()` to read the value or `.set(value)` to update it.
1510
///
1611
/// # Requirements
1712
///
18-
/// - The macro currently supports only `static mut` variables.
19-
/// - The variable type must implement `Eq`.
13+
/// - Supports only `static mut` variables.
14+
/// - Type `T` must implement `Eq`.
2015
///
2116
/// # Examples
2217
///
2318
/// ```rust
24-
/// use reactive_macros::ref_signal;
19+
/// use reactive_macros::signal;
20+
///
21+
/// signal!(static mut A: i32 = 10;);
22+
///
23+
/// assert_eq!(*A().get(), 10);
24+
/// assert!(A().set(20));
25+
/// assert_eq!(*A().get(), 20);
26+
/// assert!(!A().set(20)); // No change
2527
///
26-
/// ref_signal!(static mut A: String = "hello".to_string(););
28+
/// signal!(static mut B: String = "hello".to_string(););
2729
///
28-
/// assert_eq!(&*A_get(), "hello");
29-
/// assert!(A_set("signal".to_string()));
30-
/// assert_eq!(&*A_get(), "signal");
31-
/// assert!(!A_set("signal".to_string())); // No change
30+
/// assert_eq!(*B().get(), "hello");
31+
/// assert!(B().set("world".to_string()));
32+
/// assert_eq!(*B().get(), "world");
3233
/// ```
3334
///
3435
/// # SAFETY
@@ -51,121 +52,22 @@ use syn::{Ident, ItemFn, ItemStatic, ReturnType, parse_macro_input};
5152
/// halting problem. Therefore, you must ensure manually that effects do not
5253
/// update signals within their own dependency chain.
5354
#[proc_macro]
54-
pub fn ref_signal(input: TokenStream) -> TokenStream {
55+
pub fn signal(input: TokenStream) -> TokenStream {
5556
let item = parse_macro_input!(input as ItemStatic);
5657

57-
let attrs = &item.attrs;
5858
let vis = &item.vis;
59-
let static_token = &item.static_token;
60-
let _mutability = &item.mutability;
6159
let ident = &item.ident;
62-
let colon_token = &item.colon_token;
6360
let ty = &item.ty;
64-
let eq_token = &item.eq_token;
6561
let expr = &item.expr;
66-
let semi_token = &item.semi_token;
67-
68-
let mutability = match &item.mutability {
69-
syn::StaticMutability::Mut(_) => quote! { mut },
70-
syn::StaticMutability::None => quote! {},
71-
_ => {
72-
return syn::Error::new_spanned(&item.mutability, "Mutability not supported")
73-
.to_compile_error()
74-
.into();
75-
}
76-
};
77-
78-
let ident_p = format_ident!("_{}", ident.to_string().to_uppercase());
79-
let ident_get = format_ident!("{}_get", ident);
80-
let ident_set = format_ident!("{}_set", ident);
8162

8263
let lazy_ty = quote! { reactive_cache::Lazy<std::rc::Rc<reactive_cache::Signal<#ty>>> };
8364
let expr = quote! { reactive_cache::Lazy::new(|| reactive_cache::Signal::new(#expr)) };
8465

8566
let expanded = quote! {
86-
#(#attrs)*
87-
#vis #static_token #mutability #ident_p #colon_token #lazy_ty #eq_token #expr #semi_token
88-
89-
#[allow(non_snake_case)]
90-
pub fn #ident_get() -> std::cell::Ref<'static, #ty> {
91-
unsafe { #ident_p.get() }
92-
}
93-
94-
#[allow(non_snake_case)]
95-
pub fn #ident_set(value: #ty) -> bool {
96-
unsafe { #ident_p.set(value) }
97-
}
98-
};
99-
100-
expanded.into()
101-
}
102-
103-
/// Wraps a `static mut` variable as a reactive signal (similar to a property)
104-
/// with getter and setter functions.
105-
///
106-
/// The `signal!` macro transforms a `static mut` variable into a `reactive_cache::Signal`,
107-
/// and automatically generates:
108-
/// 1. A `_get()` function that returns a reference to the value, allowing read access.
109-
/// - This reference behaves like a normal immutable reference for most purposes.
110-
/// 2. A `_set(value)` function to write the value (returns `true` if changed).
111-
/// 3. A function with the same name as the variable that directly returns the value
112-
/// by dereferencing the underlying variable. This requires the type to implement `Copy`.
113-
///
114-
/// # Requirements
115-
///
116-
/// - The macro currently supports only `static mut` variables.
117-
/// - The variable type must implement `Eq`.
118-
///
119-
/// # Examples
120-
///
121-
/// ```rust
122-
/// use reactive_macros::{ref_signal, signal};
123-
///
124-
/// signal!(static mut A: i32 = 10;);
125-
///
126-
/// assert_eq!(A(), 10); // returns value directly (requires Copy)
127-
/// assert_eq!(*A_get(), 10); // returns a reference to the value
128-
/// assert!(A_set(20));
129-
/// assert_eq!(A(), 20);
130-
/// assert!(!A_set(20)); // No change
131-
/// ```
132-
///
133-
/// # SAFETY
134-
///
135-
/// This macro wraps `static mut` variables internally, so it **is not thread-safe**.
136-
/// It should be used only in single-threaded contexts.
137-
///
138-
/// # Warning
139-
///
140-
/// **Do not set any signal that is part of the same effect chain.**
141-
///
142-
/// Effects automatically run whenever one of their dependent signals changes.
143-
/// If an effect modifies a signal that it (directly or indirectly) observes,
144-
/// it creates a circular dependency. This can lead to:
145-
/// - an infinite loop of updates, or
146-
/// - conflicting updates that the system cannot resolve.
147-
///
148-
/// In the general case, it is impossible to automatically determine whether
149-
/// such an effect will ever terminate—this is essentially a version of the
150-
/// halting problem. Therefore, you must ensure manually that effects do not
151-
/// update signals within their own dependency chain.
152-
#[proc_macro]
153-
pub fn signal(input: TokenStream) -> TokenStream {
154-
let input_clone: proc_macro2::TokenStream = input.clone().into();
155-
156-
let item = parse_macro_input!(input as ItemStatic);
157-
let ident = &item.ident;
158-
let ty = &item.ty;
159-
160-
let ident_p = format_ident!("_{}", ident.to_string().to_uppercase());
161-
let ident_fn = format_ident!("{}", ident);
162-
163-
let expanded = quote! {
164-
ref_signal!(#input_clone);
165-
16667
#[allow(non_snake_case)]
167-
pub fn #ident_fn() -> #ty {
168-
*unsafe { #ident_p.get() }
68+
#vis fn #ident() -> &'static std::rc::Rc<reactive_cache::Signal<#ty>> {
69+
static mut #ident: #lazy_ty = #expr;
70+
unsafe { &*#ident }
16971
}
17072
};
17173

@@ -252,12 +154,9 @@ pub fn memo(_attr: TokenStream, item: TokenStream) -> TokenStream {
252154
let expr = quote! { reactive_cache::Lazy::new(|| reactive_cache::Memo::new(|| #block)) };
253155

254156
let expanded = quote! {
255-
static mut #ident: #ty = #expr;
256-
257-
#vis #sig
258-
where #output_ty: Clone
259-
{
260-
unsafe { (*#ident).get() }
157+
#vis #sig {
158+
static mut #ident: #ty = #expr;
159+
unsafe { #ident.get() }
261160
}
262161
};
263162

@@ -281,7 +180,7 @@ pub fn memo(_attr: TokenStream, item: TokenStream) -> TokenStream {
281180
/// # Examples
282181
///
283182
/// ```rust
284-
/// use reactive_macros::{evaluate, ref_signal};
183+
/// use reactive_macros::evaluate;
285184
///
286185
/// fn print(msg: String) {
287186
/// println!("{}", msg);

macros/tests/dep.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
use std::cell::Cell;
44

5-
use reactive_macros::{memo, ref_signal, signal};
5+
use reactive_macros::{memo, signal};
66

77
thread_local! {
88
static SOURCE_A_CALLED: Cell<bool> = const { Cell::new(false) };
@@ -16,20 +16,20 @@ signal!(
1616
static mut A: i32 = 10;
1717
);
1818

19-
ref_signal!(
19+
signal!(
2020
static mut B: String = 5.to_string();
2121
);
2222

2323
pub fn source_a() -> i32 {
2424
SOURCE_A_CALLED.set(true);
2525

26-
A()
26+
*A().get()
2727
}
2828

2929
pub fn source_b() -> i32 {
3030
SOURCE_B_CALLED.set(true);
3131

32-
B_get().parse::<i32>().unwrap()
32+
B().get().parse::<i32>().unwrap()
3333
}
3434

3535
#[memo]
@@ -137,7 +137,7 @@ fn signal_set_unchanged_test() {
137137
DERIVED_D_CALLED.set(false);
138138
DERIVED_E_CALLED.set(false);
139139

140-
A_set(10);
140+
A().set(10);
141141

142142
assert!(!SOURCE_A_CALLED.get());
143143
assert!(!SOURCE_B_CALLED.get());
@@ -182,8 +182,8 @@ fn signal_set_value_test() {
182182
DERIVED_D_CALLED.set(false);
183183
DERIVED_E_CALLED.set(false);
184184

185-
A_set(20);
186-
A_set(10);
185+
A().set(20);
186+
A().set(10);
187187

188188
assert!(!SOURCE_A_CALLED.get());
189189
assert!(!SOURCE_B_CALLED.get());

0 commit comments

Comments
 (0)