Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a derive macro for create_slice() #1867

Merged
merged 11 commits into from
Oct 24, 2023
36 changes: 35 additions & 1 deletion leptos_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ mod view;
use view::{client_template::render_template, render_view};
mod component;
mod server;
mod slice;
mod slot;

/// The `view` macro uses RSX (like JSX, but Rust!) It follows most of the
Expand Down Expand Up @@ -958,7 +959,7 @@ pub fn params_derive(
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
match syn::parse(input) {
Ok(ast) => params::impl_params(&ast),
Ok(ast) => params::params_impl(&ast),
Err(err) => err.to_compile_error().into(),
}
}
Expand All @@ -969,3 +970,36 @@ pub(crate) fn attribute_value(attr: &KeyedAttribute) -> &syn::Expr {
None => abort!(attr.key, "attribute should have value"),
}
}

/// Generates a `lens` into struct with a default getter and setter
///
/// Can be used to access deeply nested fields within a global state object
///
/// ```rust
/// # use leptos::{create_runtime, create_rw_signal};
/// # use leptos_macro::slice;
/// # let runtime = create_runtime();
///
/// #[derive(Default)]
/// pub struct Outer {
/// count: i32,
/// inner: Inner,
/// }
///
/// #[derive(Default)]
/// pub struct Inner {
/// inner_count: i32,
/// inner_name: String,
/// }
///
/// let outer_signal = create_rw_signal(Outer::default());
///
/// let (count, set_count) = slice!(outer_signal.count);
///
/// let (inner_count, set_inner_count) = slice!(outer_signal.inner.inner_count);
/// let (inner_name, set_inner_name) = slice!(outer_signal.inner.inner_name);
/// ```
#[proc_macro]
pub fn slice(input: TokenStream) -> TokenStream {
slice::slice_impl(input)
}
2 changes: 1 addition & 1 deletion leptos_macro/src/params.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;

pub fn impl_params(ast: &syn::DeriveInput) -> proc_macro::TokenStream {
pub fn params_impl(ast: &syn::DeriveInput) -> proc_macro::TokenStream {
let name = &ast.ident;

let fields = if let syn::Data::Struct(syn::DataStruct {
Expand Down
59 changes: 59 additions & 0 deletions leptos_macro/src/slice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use syn::{
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
token::Dot,
Token, Type,
};

struct SliceMacroInput {
pub root: Ident,
pub path: Punctuated<Type, Dot>,
}

impl Parse for SliceMacroInput {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
let root = syn::Ident::parse(input)?;
let _dot = <Token![.]>::parse(input)?;
let path = input.parse_terminated(Type::parse, Token![.])?;

if path.is_empty() {
return Err(syn::Error::new(input.span(), "Expected identifier"));
}

if path.trailing_punct() {
return Err(syn::Error::new(
input.span(),
"Unexpected trailing `.`",
));
}

Ok(SliceMacroInput { root, path })
}
}

impl From<SliceMacroInput> for TokenStream {
fn from(val: SliceMacroInput) -> Self {
let root = val.root;
let path = val.path;

quote! {
::leptos::create_slice(
#root,
|st: &_| st.#path.clone(),
|st: &mut _, n| st.#path = n
)
}
.into()
}
}

pub fn slice_impl(tokens: TokenStream) -> TokenStream {
let input = parse_macro_input!(tokens as SliceMacroInput);
input.into()
}
32 changes: 32 additions & 0 deletions leptos_macro/tests/slice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use leptos::{create_runtime, create_rw_signal};
use leptos_macro::slice;

#[derive(Default)]
pub struct OuterState {
count: i32,
inner: InnerState,
}

#[derive(Clone, PartialEq, Default)]
pub struct InnerState {
inner_count: i32,
inner_name: String,
}

#[test]
fn green() {
let _ = create_runtime();

let outer_signal = create_rw_signal(OuterState::default());

let (_, _) = slice!(outer_signal.count);

let (_, _) = slice!(outer_signal.inner.inner_count);
let (_, _) = slice!(outer_signal.inner.inner_name);
}

#[test]
fn red() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/slice/red.rs")
}
28 changes: 28 additions & 0 deletions leptos_macro/tests/slice/red.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use leptos::{create_runtime, create_rw_signal};
use leptos_macro::slice;

#[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 _ = create_runtime();

let outer_signal = create_rw_signal(OuterState::default());

let (_, _) = slice!();

let (_, _) = slice!(outer_signal);

let (_, _) = slice!(outer_signal.);

let (_, _) = slice!(outer_signal.inner.);
}
31 changes: 31 additions & 0 deletions leptos_macro/tests/slice/red.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
error: unexpected end of input, expected identifier
--> tests/slice/red.rs:21:18
|
21 | let (_, _) = slice!();
| ^^^^^^^^
|
= note: this error originates in the macro `slice` (in Nightly builds, run with -Z macro-backtrace for more info)

error: expected `.`
--> tests/slice/red.rs:23:18
|
23 | let (_, _) = slice!(outer_signal);
| ^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `slice` (in Nightly builds, run with -Z macro-backtrace for more info)

error: Expected identifier
--> tests/slice/red.rs:25:18
|
25 | let (_, _) = slice!(outer_signal.);
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `slice` (in Nightly builds, run with -Z macro-backtrace for more info)

error: Unexpected trailing `.`
--> tests/slice/red.rs:27:18
|
27 | let (_, _) = slice!(outer_signal.inner.);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `slice` (in Nightly builds, run with -Z macro-backtrace for more info)
Loading