Skip to content

Commit

Permalink
feat: add memo! macro (closes #2708) (#2826)
Browse files Browse the repository at this point in the history
  • Loading branch information
sabify authored Aug 14, 2024
1 parent aa49ad7 commit 66d7cb5
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 0 deletions.
33 changes: 33 additions & 0 deletions leptos_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 changes: 56 additions & 0 deletions leptos_macro/src/memo.rs
Original file line number Diff line number Diff line change
@@ -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()
}
33 changes: 33 additions & 0 deletions leptos_macro/tests/memo.rs
Original file line number Diff line number Diff line change
@@ -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")
}
26 changes: 26 additions & 0 deletions leptos_macro/tests/memo/red.rs
Original file line number Diff line number Diff line change
@@ -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.);
}
31 changes: 31 additions & 0 deletions leptos_macro/tests/memo/red.stderr
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 66d7cb5

Please sign in to comment.