@@ -5,11 +5,13 @@ use syn::{Ident, ItemFn, ItemStatic, ReturnType, parse_macro_input};
55/// Wraps a `static mut` variable as a reactive signal (similar to a property)
66/// with getter and setter functions.
77///
8- /// The `signal !` macro transforms a `static mut` variable into a `reactive_cache::Signal`,
8+ /// The `ref_signal !` macro transforms a `static mut` variable into a `reactive_cache::Signal`,
99/// and automatically generates:
10- /// 1. A `_get()` function to read the value.
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.
1112/// 2. A `_set(value)` function to write the value (returns `true` if changed).
12- /// 3. A function with the same name as the variable to simplify access (calls `_get()`).
13+ ///
14+ /// Unlike `signal!`, `ref_signal!` does **not** generate a same-named function that directly returns the value.
1315///
1416/// # Requirements
1517///
@@ -19,16 +21,14 @@ use syn::{Ident, ItemFn, ItemStatic, ReturnType, parse_macro_input};
1921/// # Examples
2022///
2123/// ```rust
22- /// use std::cell::Cell;
23- /// use reactive_macros::signal;
24+ /// use reactive_macros::ref_signal;
2425///
25- /// signal !(static mut A: i32 = 10 ;);
26+ /// ref_signal !(static mut A: String = "hello".to_string() ;);
2627///
27- /// assert_eq!(*A(), 10);
28- /// assert_eq!(*A_get(), 10);
29- /// assert!(A_set(20));
30- /// assert_eq!(*A(), 20);
31- /// assert!(!A_set(20)); // No change
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
3232/// ```
3333///
3434/// # SAFETY
@@ -51,7 +51,7 @@ use syn::{Ident, ItemFn, ItemStatic, ReturnType, parse_macro_input};
5151/// halting problem. Therefore, you must ensure manually that effects do not
5252/// update signals within their own dependency chain.
5353#[ proc_macro]
54- pub fn signal ( input : TokenStream ) -> TokenStream {
54+ pub fn ref_signal ( input : TokenStream ) -> TokenStream {
5555 let item = parse_macro_input ! ( input as ItemStatic ) ;
5656
5757 let attrs = & item. attrs ;
@@ -78,7 +78,6 @@ pub fn signal(input: TokenStream) -> TokenStream {
7878 let ident_p = format_ident ! ( "_{}" , ident. to_string( ) . to_uppercase( ) ) ;
7979 let ident_get = format_ident ! ( "{}_get" , ident) ;
8080 let ident_set = format_ident ! ( "{}_set" , ident) ;
81- let ident_fn = format_ident ! ( "{}" , ident) ;
8281
8382 let lazy_ty = quote ! { reactive_cache:: Lazy <reactive_cache:: Signal <#ty>> } ;
8483 let expr = quote ! { reactive_cache:: Lazy :: new( || reactive_cache:: Signal :: new( Some ( #expr) ) ) } ;
@@ -96,10 +95,77 @@ pub fn signal(input: TokenStream) -> TokenStream {
9695 pub fn #ident_set( value: #ty) -> bool {
9796 unsafe { #ident_p. set( value) }
9897 }
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 + Default`.
118+ ///
119+ /// # Examples
120+ ///
121+ /// ```rust
122+ /// use reactive_macros::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+ reactive_macros:: ref_signal!( #input_clone) ;
99165
100166 #[ allow( non_snake_case) ]
101- pub fn #ident_fn( ) -> std :: cell :: Ref < ' static , #ty> {
102- #ident_get ( )
167+ pub fn #ident_fn( ) -> #ty {
168+ * unsafe { #ident_p . get ( ) }
103169 }
104170 } ;
105171
0 commit comments