feat: add memo! macro (closes #2708) (#2826)

This commit is contained in:
Saber Haj Rabiee 2024-08-14 06:14:17 -07:00 committed by GitHub
parent aa49ad760b
commit 66d7cb5d12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 179 additions and 0 deletions

View file

@ -19,6 +19,7 @@ mod params;
mod view;
use crate::component::unmodified_fn_name_from_fn_name;
mod component;
mod memo;
mod slice;
mod slot;
@ -875,3 +876,35 @@ pub fn params_derive(
pub fn slice(input: TokenStream) -> TokenStream {
slice::slice_impl(input)
}
/// Generates a `memo` into a struct with a default getter.
///
/// Can be used to access deeply nested fields within a global state object.
///
/// ```rust
/// # use leptos::prelude::*;
/// # use leptos_macro::memo;
///
/// #[derive(Default)]
/// pub struct Outer {
/// count: i32,
/// inner: Inner,
/// }
///
/// #[derive(Default)]
/// pub struct Inner {
/// inner_count: i32,
/// inner_name: String,
/// }
///
/// let outer_signal = RwSignal::new(Outer::default());
///
/// let count = memo!(outer_signal.count);
///
/// let inner_count = memo!(outer_signal.inner.inner_count);
/// let inner_name = memo!(outer_signal.inner.inner_name);
/// ```
#[proc_macro]
pub fn memo(input: TokenStream) -> TokenStream {
memo::memo_impl(input)
}

56
leptos_macro/src/memo.rs Normal file
View file

@ -0,0 +1,56 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
Token,
};
struct MemoMacroInput {
root: syn::Ident,
path: Punctuated<syn::Member, Token![.]>,
}
impl Parse for MemoMacroInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let root: syn::Ident = input.parse()?;
input.parse::<Token![.]>()?;
// do not accept trailing punctuation
let path: Punctuated<syn::Member, Token![.]> =
Punctuated::parse_separated_nonempty(input)?;
if path.is_empty() {
return Err(input.error("expected identifier"));
}
if !input.is_empty() {
return Err(input.error("unexpected token"));
}
Ok(Self { root, path })
}
}
impl ToTokens for MemoMacroInput {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let root = &self.root;
let path = &self.path;
tokens.extend(quote! {
::leptos::reactive_graph::computed::Memo::new(
move |_| {
use ::leptos::reactive_graph::traits::With;
#root.with(|st: _| st.#path.clone())
}
)
})
}
}
pub fn memo_impl(tokens: TokenStream) -> TokenStream {
let input = parse_macro_input!(tokens as MemoMacroInput);
input.into_token_stream().into()
}

View file

@ -0,0 +1,33 @@
use leptos::prelude::RwSignal;
use leptos_macro::memo;
#[derive(Default)]
pub struct OuterState {
count: i32,
inner: InnerState,
}
#[derive(Clone, PartialEq, Default)]
pub struct InnerState {
inner_count: i32,
inner_tuple: InnerTuple,
}
#[derive(Clone, PartialEq, Default)]
pub struct InnerTuple(String);
#[test]
fn green() {
let outer_signal = RwSignal::new(OuterState::default());
let _ = memo!(outer_signal.count);
let _ = memo!(outer_signal.inner.inner_count);
let _ = memo!(outer_signal.inner.inner_tuple.0);
}
#[test]
fn red() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/memo/red.rs")
}

View file

@ -0,0 +1,26 @@
use leptos::prelude::RwSignal;
use leptos_macro::memo;
#[derive(Default, PartialEq)]
pub struct OuterState {
count: i32,
inner: InnerState,
}
#[derive(Clone, PartialEq, Default)]
pub struct InnerState {
inner_count: i32,
inner_name: String,
}
fn main() {
let outer_signal = RwSignal::new(OuterState::default());
let _ = memo!();
let _ = memo!(outer_signal);
let _ = memo!(outer_signal.);
let _ = memo!(outer_signal.inner.);
}

View file

@ -0,0 +1,31 @@
error: unexpected end of input, expected identifier
--> tests/memo/red.rs:19:13
|
19 | let _ = memo!();
| ^^^^^^^
|
= note: this error originates in the macro `memo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected `.`
--> tests/memo/red.rs:21:13
|
21 | let _ = memo!(outer_signal);
| ^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `memo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected end of input, expected identifier or integer
--> tests/memo/red.rs:23:13
|
23 | let _ = memo!(outer_signal.);
| ^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `memo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected end of input, expected identifier or integer
--> tests/memo/red.rs:25:13
|
25 | let _ = memo!(outer_signal.inner.);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `memo` (in Nightly builds, run with -Z macro-backtrace for more info)