From 1b6e80cde16dc6f02f37c55a517a110accc5c347 Mon Sep 17 00:00:00 2001 From: SadraMoh Date: Sun, 8 Oct 2023 17:25:15 +0200 Subject: [PATCH 01/10] feat: simple case for copy fields --- leptos_macro/src/lens.rs | 38 +++++++++++++++++++++++++++ leptos_macro/src/lib.rs | 11 +++++++- leptos_macro/src/params.rs | 2 +- leptos_macro/tests/lens.rs | 20 ++++++++++++++ leptos_macro/tests/lens/field_copy.rs | 11 ++++++++ 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 leptos_macro/src/lens.rs create mode 100644 leptos_macro/tests/lens.rs create mode 100644 leptos_macro/tests/lens/field_copy.rs diff --git a/leptos_macro/src/lens.rs b/leptos_macro/src/lens.rs new file mode 100644 index 0000000000..02c1cf0a17 --- /dev/null +++ b/leptos_macro/src/lens.rs @@ -0,0 +1,38 @@ +use quote::quote; +use syn::Data; + +pub fn lens_impl(ast: &syn::DeriveInput) -> proc_macro::TokenStream { + let source_ident = &ast.ident; + + let Data::Struct(source_data) = &ast.data else { + panic!("Lens is only implemented on strcuts") + }; + + let lens_methods = source_data.fields.iter().map(|field| { + let Some(field_ident) = &field.ident else { + panic!( + "Encountered unnamed field, Lens cannot be used on tuple \ + structs" + ); + }; + + quote! { + + pub fn #field_ident( + signal: ::leptos::RwSignal, + ) -> (::leptos::Signal, ::leptos::SignalSetter) { + ::leptos::create_slice(signal, |st| st.count, |st, n| st.count = n) + } + + } + }); + + quote! { + + impl #source_ident { + #(#lens_methods)* + } + + } + .into() +} diff --git a/leptos_macro/src/lib.rs b/leptos_macro/src/lib.rs index a67247f7c3..dfb32b4fe6 100644 --- a/leptos_macro/src/lib.rs +++ b/leptos_macro/src/lib.rs @@ -33,6 +33,7 @@ mod params; mod view; use view::{client_template::render_template, render_view}; mod component; +mod lens; mod server; mod slot; @@ -954,7 +955,15 @@ 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(), + } +} + +#[proc_macro_derive(Lens)] +pub fn lens_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + match syn::parse(input) { + Ok(ast) => lens::lens_impl(&ast), Err(err) => err.to_compile_error().into(), } } diff --git a/leptos_macro/src/params.rs b/leptos_macro/src/params.rs index 79a24cbbf2..c94fc88a63 100644 --- a/leptos_macro/src/params.rs +++ b/leptos_macro/src/params.rs @@ -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 { diff --git a/leptos_macro/tests/lens.rs b/leptos_macro/tests/lens.rs new file mode 100644 index 0000000000..ab118b927d --- /dev/null +++ b/leptos_macro/tests/lens.rs @@ -0,0 +1,20 @@ +#[test] +fn lens() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/lens/field_copy.rs"); +} + +use leptos::{create_rw_signal, expect_context, provide_context, RwSignal}; +use leptos_macro::Lens; + +#[derive(Default, Lens)] +struct GlobalState { + count: i32, +} + +fn main() { + provide_context(create_rw_signal(GlobalState::default())); + let state = expect_context::>(); + + let (read, write) = GlobalState::count(state); +} diff --git a/leptos_macro/tests/lens/field_copy.rs b/leptos_macro/tests/lens/field_copy.rs new file mode 100644 index 0000000000..c4de58245a --- /dev/null +++ b/leptos_macro/tests/lens/field_copy.rs @@ -0,0 +1,11 @@ +use leptos_macro::Lens; + +#[derive(Lens)] +struct GlobalState { + count: i32, +} + +fn main() { + let state = create_rw_signal(GlobalState::default()); + let _ = GlobalState::count(state); +} From 7c7c0a21a3847143b818d520e490615fce14e7ab Mon Sep 17 00:00:00 2001 From: SadraMoh Date: Sun, 8 Oct 2023 17:43:15 +0200 Subject: [PATCH 02/10] feat: initial testing --- leptos_macro/tests/lens.rs | 17 +---------------- leptos_macro/tests/lens/field_copy.rs | 5 ++++- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/leptos_macro/tests/lens.rs b/leptos_macro/tests/lens.rs index ab118b927d..3056d88fdc 100644 --- a/leptos_macro/tests/lens.rs +++ b/leptos_macro/tests/lens.rs @@ -1,20 +1,5 @@ #[test] fn lens() { let t = trybuild::TestCases::new(); - t.compile_fail("tests/lens/field_copy.rs"); -} - -use leptos::{create_rw_signal, expect_context, provide_context, RwSignal}; -use leptos_macro::Lens; - -#[derive(Default, Lens)] -struct GlobalState { - count: i32, -} - -fn main() { - provide_context(create_rw_signal(GlobalState::default())); - let state = expect_context::>(); - - let (read, write) = GlobalState::count(state); + t.pass("tests/lens/field_copy.rs"); } diff --git a/leptos_macro/tests/lens/field_copy.rs b/leptos_macro/tests/lens/field_copy.rs index c4de58245a..c6b0d66d8b 100644 --- a/leptos_macro/tests/lens/field_copy.rs +++ b/leptos_macro/tests/lens/field_copy.rs @@ -1,3 +1,4 @@ +use leptos::*; use leptos_macro::Lens; #[derive(Lens)] @@ -6,6 +7,8 @@ struct GlobalState { } fn main() { - let state = create_rw_signal(GlobalState::default()); + let _ = create_runtime(); + + let state = create_rw_signal(GlobalState { count: 0 }); let _ = GlobalState::count(state); } From 6d44fda3d3138c3525e71c937fad4daed4c81969 Mon Sep 17 00:00:00 2001 From: SadraMoh Date: Sun, 8 Oct 2023 17:55:44 +0200 Subject: [PATCH 03/10] feat: initial testing --- leptos_macro/src/lens.rs | 10 +++++----- leptos_macro/tests/lens/field_copy.rs | 28 ++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/leptos_macro/src/lens.rs b/leptos_macro/src/lens.rs index 02c1cf0a17..01b5d01331 100644 --- a/leptos_macro/src/lens.rs +++ b/leptos_macro/src/lens.rs @@ -18,11 +18,11 @@ pub fn lens_impl(ast: &syn::DeriveInput) -> proc_macro::TokenStream { quote! { - pub fn #field_ident( - signal: ::leptos::RwSignal, - ) -> (::leptos::Signal, ::leptos::SignalSetter) { - ::leptos::create_slice(signal, |st| st.count, |st, n| st.count = n) - } + pub fn #field_ident( + signal: ::leptos::RwSignal, + ) -> (::leptos::Signal, ::leptos::SignalSetter) { + ::leptos::create_slice(signal, |st| st.count, |st, n| st.count = n) + } } }); diff --git a/leptos_macro/tests/lens/field_copy.rs b/leptos_macro/tests/lens/field_copy.rs index c6b0d66d8b..1971165218 100644 --- a/leptos_macro/tests/lens/field_copy.rs +++ b/leptos_macro/tests/lens/field_copy.rs @@ -1,14 +1,32 @@ use leptos::*; use leptos_macro::Lens; -#[derive(Lens)] -struct GlobalState { - count: i32, +fn single_field() { + #[derive(Default, Lens)] + struct GlobalState { + count: i32, + } + + let _ = create_runtime(); + + let state = create_rw_signal(GlobalState::default()); + let _ = GlobalState::count(state); } -fn main() { +fn multiple_fields() { + #[derive(Default, Lens)] + struct GlobalState { + count: i32, + age: u32, + } + let _ = create_runtime(); - let state = create_rw_signal(GlobalState { count: 0 }); + let state = create_rw_signal(GlobalState::default()); let _ = GlobalState::count(state); } + +fn main() { + single_field(); + multiple_fields(); +} From ac9067e0f7ff88fb96dd10e9349126145fcd1838 Mon Sep 17 00:00:00 2001 From: SadraMoh Date: Sun, 8 Oct 2023 18:01:29 +0200 Subject: [PATCH 04/10] fix: lens and field incompatible types --- leptos_macro/src/lens.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/leptos_macro/src/lens.rs b/leptos_macro/src/lens.rs index 01b5d01331..216fa4e7a3 100644 --- a/leptos_macro/src/lens.rs +++ b/leptos_macro/src/lens.rs @@ -16,12 +16,14 @@ pub fn lens_impl(ast: &syn::DeriveInput) -> proc_macro::TokenStream { ); }; + let field_ty = &field.ty; + quote! { pub fn #field_ident( signal: ::leptos::RwSignal, - ) -> (::leptos::Signal, ::leptos::SignalSetter) { - ::leptos::create_slice(signal, |st| st.count, |st, n| st.count = n) + ) -> (::leptos::Signal<#field_ty>, ::leptos::SignalSetter<#field_ty>) { + ::leptos::create_slice(signal, |st: Self| st.count, |st: Self, n: #field_ty| st.count = n) } } From 12470ea1e3dbf1b4627f1a5127e4d4e4962be463 Mon Sep 17 00:00:00 2001 From: SadraMoh Date: Sun, 8 Oct 2023 18:09:01 +0200 Subject: [PATCH 05/10] fix: incompatible field names --- leptos_macro/src/lens.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leptos_macro/src/lens.rs b/leptos_macro/src/lens.rs index 216fa4e7a3..afa76ae69a 100644 --- a/leptos_macro/src/lens.rs +++ b/leptos_macro/src/lens.rs @@ -23,7 +23,7 @@ pub fn lens_impl(ast: &syn::DeriveInput) -> proc_macro::TokenStream { pub fn #field_ident( signal: ::leptos::RwSignal, ) -> (::leptos::Signal<#field_ty>, ::leptos::SignalSetter<#field_ty>) { - ::leptos::create_slice(signal, |st: Self| st.count, |st: Self, n: #field_ty| st.count = n) + ::leptos::create_slice(signal, |st: Self| st.#field_ident, |st: Self, n: #field_ty| st.#field_ident = n) } } From ea20108632a850ecc4e1bd7ebd30ba9fcb8dd5a4 Mon Sep 17 00:00:00 2001 From: SadraMoh Date: Sun, 8 Oct 2023 18:18:28 +0200 Subject: [PATCH 06/10] fix: incorrect closure types --- leptos_macro/src/lens.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leptos_macro/src/lens.rs b/leptos_macro/src/lens.rs index afa76ae69a..9f2f8c7f6f 100644 --- a/leptos_macro/src/lens.rs +++ b/leptos_macro/src/lens.rs @@ -23,7 +23,7 @@ pub fn lens_impl(ast: &syn::DeriveInput) -> proc_macro::TokenStream { pub fn #field_ident( signal: ::leptos::RwSignal, ) -> (::leptos::Signal<#field_ty>, ::leptos::SignalSetter<#field_ty>) { - ::leptos::create_slice(signal, |st: Self| st.#field_ident, |st: Self, n: #field_ty| st.#field_ident = n) + ::leptos::create_slice(signal, |st: &Self| st.#field_ident, |st: &mut Self, n: #field_ty| st.#field_ident = n) } } From 9a82909f58ee81d02cb913eb0f6c3c78e9b193df Mon Sep 17 00:00:00 2001 From: SadraMoh Date: Sun, 8 Oct 2023 20:21:13 +0200 Subject: [PATCH 07/10] feat: regression tests --- leptos_macro/src/lens.rs | 75 ++++++++++++++++------- leptos_macro/tests/lens.rs | 29 ++++++++- leptos_macro/tests/lens/field_copy.rs | 32 ---------- leptos_macro/tests/lens/structures.rs | 17 +++++ leptos_macro/tests/lens/structures.stderr | 15 +++++ 5 files changed, 112 insertions(+), 56 deletions(-) delete mode 100644 leptos_macro/tests/lens/field_copy.rs create mode 100644 leptos_macro/tests/lens/structures.rs create mode 100644 leptos_macro/tests/lens/structures.stderr diff --git a/leptos_macro/src/lens.rs b/leptos_macro/src/lens.rs index 9f2f8c7f6f..867a49fba4 100644 --- a/leptos_macro/src/lens.rs +++ b/leptos_macro/src/lens.rs @@ -1,33 +1,64 @@ +use proc_macro2::Ident; use quote::quote; -use syn::Data; +use syn::{spanned::Spanned, Data, Field}; -pub fn lens_impl(ast: &syn::DeriveInput) -> proc_macro::TokenStream { - let source_ident = &ast.ident; +fn named_field_method(field: &Field, name: &Ident) -> proc_macro2::TokenStream { + let field_accessor = name; + let field_ty = &field.ty; - let Data::Struct(source_data) = &ast.data else { - panic!("Lens is only implemented on strcuts") - }; + quote! { + pub fn #name( + signal: ::leptos::RwSignal, + ) -> (::leptos::Signal<#field_ty>, ::leptos::SignalSetter<#field_ty>) { + ::leptos::create_slice( + signal, + |st: &Self| st.#field_accessor.clone(), + |st: &mut Self, n: #field_ty| st.#field_accessor = n + ) + } + } +} - let lens_methods = source_data.fields.iter().map(|field| { - let Some(field_ident) = &field.ident else { - panic!( - "Encountered unnamed field, Lens cannot be used on tuple \ - structs" - ); - }; +fn unnamed_field_method( + field: &Field, + index: usize, +) -> proc_macro2::TokenStream { + let number_ident = syn::Ident::new(&format!("_{index}"), field.span()); + let field_accessor = syn::Index::from(index); + let field_ty = &field.ty; - let field_ty = &field.ty; + quote! { + pub fn #number_ident( + signal: ::leptos::RwSignal, + ) -> (::leptos::Signal<#field_ty>, ::leptos::SignalSetter<#field_ty>) { + ::leptos::create_slice( + signal, + |st: &Self| st.#field_accessor.clone(), + |st: &mut Self, n: #field_ty| st.#field_accessor = n + ) + } + } +} - quote! { +pub fn lens_impl(ast: &syn::DeriveInput) -> proc_macro::TokenStream { + let source_ident = &ast.ident; - pub fn #field_ident( - signal: ::leptos::RwSignal, - ) -> (::leptos::Signal<#field_ty>, ::leptos::SignalSetter<#field_ty>) { - ::leptos::create_slice(signal, |st: &Self| st.#field_ident, |st: &mut Self, n: #field_ty| st.#field_ident = n) - } + let Data::Struct(source_data) = &ast.data else { + panic!("Lens cannot be derived by enums or unions") + }; - } - }); + let lens_methods = + source_data + .fields + .iter() + .enumerate() + .map(|(field_index, field)| { + if let Some(field_name) = &field.ident { + named_field_method(&field, field_name) + } else { + unnamed_field_method(&field, field_index) + } + }); quote! { diff --git a/leptos_macro/tests/lens.rs b/leptos_macro/tests/lens.rs index 3056d88fdc..955946c25e 100644 --- a/leptos_macro/tests/lens.rs +++ b/leptos_macro/tests/lens.rs @@ -1,5 +1,30 @@ +use leptos::{create_runtime, create_rw_signal}; +use leptos_macro::Lens; + +#[derive(Lens, Default)] +struct FieldStruct { + age: u32, + name: String, +} + +#[derive(Lens, Default)] +struct TupleStruct(u32, String); + +#[test] +fn green() { + let _ = create_runtime(); + + let field_signal = create_rw_signal(FieldStruct::default()); + let _ = FieldStruct::age(field_signal); + let _ = FieldStruct::name(field_signal); + + let tuple_signal = create_rw_signal(TupleStruct::default()); + let _ = TupleStruct::_0(tuple_signal); + let _ = TupleStruct::_1(tuple_signal); +} + #[test] -fn lens() { +fn red() { let t = trybuild::TestCases::new(); - t.pass("tests/lens/field_copy.rs"); + t.compile_fail("tests/lens/structures.rs") } diff --git a/leptos_macro/tests/lens/field_copy.rs b/leptos_macro/tests/lens/field_copy.rs deleted file mode 100644 index 1971165218..0000000000 --- a/leptos_macro/tests/lens/field_copy.rs +++ /dev/null @@ -1,32 +0,0 @@ -use leptos::*; -use leptos_macro::Lens; - -fn single_field() { - #[derive(Default, Lens)] - struct GlobalState { - count: i32, - } - - let _ = create_runtime(); - - let state = create_rw_signal(GlobalState::default()); - let _ = GlobalState::count(state); -} - -fn multiple_fields() { - #[derive(Default, Lens)] - struct GlobalState { - count: i32, - age: u32, - } - - let _ = create_runtime(); - - let state = create_rw_signal(GlobalState::default()); - let _ = GlobalState::count(state); -} - -fn main() { - single_field(); - multiple_fields(); -} diff --git a/leptos_macro/tests/lens/structures.rs b/leptos_macro/tests/lens/structures.rs new file mode 100644 index 0000000000..0c5f637154 --- /dev/null +++ b/leptos_macro/tests/lens/structures.rs @@ -0,0 +1,17 @@ +use leptos_macro::Lens; + +#[derive(Lens)] +enum FailOnEnum { + This(u32), + Should(String), + Panic, +} + +#[derive(Lens)] +union FaulOnUnion { + this: u32, + should: std::mem::ManuallyDrop, + panic: std::mem::ManuallyDrop>, +} + +fn main() {} diff --git a/leptos_macro/tests/lens/structures.stderr b/leptos_macro/tests/lens/structures.stderr new file mode 100644 index 0000000000..7dbda38229 --- /dev/null +++ b/leptos_macro/tests/lens/structures.stderr @@ -0,0 +1,15 @@ +error: proc-macro derive panicked + --> tests/lens/structures.rs:3:10 + | +3 | #[derive(Lens)] + | ^^^^ + | + = help: message: Lens cannot be derived by enums or unions + +error: proc-macro derive panicked + --> tests/lens/structures.rs:10:10 + | +10 | #[derive(Lens)] + | ^^^^ + | + = help: message: Lens cannot be derived by enums or unions From 56d14cd85fc67ccd16974972f276a0bd51d1f707 Mon Sep 17 00:00:00 2001 From: SadraMoh Date: Sun, 8 Oct 2023 21:22:55 +0200 Subject: [PATCH 08/10] feat: export from library and documentation --- leptos_macro/src/lib.rs | 48 +++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/leptos_macro/src/lib.rs b/leptos_macro/src/lib.rs index dfb32b4fe6..4b03a777bf 100644 --- a/leptos_macro/src/lib.rs +++ b/leptos_macro/src/lib.rs @@ -960,6 +960,47 @@ pub fn params_derive( } } +pub(crate) fn attribute_value(attr: &KeyedAttribute) -> &syn::Expr { + match attr.value() { + Some(value) => value, + None => abort!(attr.key, "attribute should have value"), + } +} + +/// Generates a function for each field of the deriving struct that calls +/// [create_slice](https://docs.rs/leptos/latest/leptos/fn.create_slice.html) with a default getter and setter. +/// +/// ```rust +/// # use leptos::{create_runtime, create_rw_signal}; +/// # use leptos_macro::Lens; +/// # let runtime = create_runtime(); +/// +/// // some global state with independent fields +/// #[derive(Lens, Default)] +/// pub struct GlobalState { +/// count: i32, +/// name: String, +/// } +/// +/// let state = create_rw_signal(GlobalState::default()); +/// let (count, set_count) = GlobalState::count(state); +/// ``` +/// +/// for tuple structs +/// +/// ```rust +/// # use leptos::{create_runtime, create_rw_signal}; +/// # use leptos_macro::Lens; +/// # let runtime = create_runtime(); +/// +/// // global state in the form of a tuple struct +/// #[derive(Lens, Default)] +/// pub struct GlobalState(i32, String); +/// +/// let state = create_rw_signal(GlobalState::default()); +/// let (number, set_number) = GlobalState::_0(state); +/// let (str, set_str) = GlobalState::_1(state); +/// ``` #[proc_macro_derive(Lens)] pub fn lens_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { match syn::parse(input) { @@ -967,10 +1008,3 @@ pub fn lens_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { Err(err) => err.to_compile_error().into(), } } - -pub(crate) fn attribute_value(attr: &KeyedAttribute) -> &syn::Expr { - match attr.value() { - Some(value) => value, - None => abort!(attr.key, "attribute should have value"), - } -} From a83c9fe290cb633ccc97bbbafb36e9520f4bf5ff Mon Sep 17 00:00:00 2001 From: SadraMoh Date: Tue, 10 Oct 2023 23:56:22 +0200 Subject: [PATCH 09/10] feat: poke macro to generate lenses on the fly --- leptos_macro/src/lens.rs | 4 ++-- leptos_macro/src/lib.rs | 34 +++++++++++++++++++++++++++ leptos_macro/src/poke.rs | 48 ++++++++++++++++++++++++++++++++++++++ leptos_macro/tests/lens.rs | 21 ++++++++++++++++- 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 leptos_macro/src/poke.rs diff --git a/leptos_macro/src/lens.rs b/leptos_macro/src/lens.rs index 867a49fba4..05329ecf86 100644 --- a/leptos_macro/src/lens.rs +++ b/leptos_macro/src/lens.rs @@ -54,9 +54,9 @@ pub fn lens_impl(ast: &syn::DeriveInput) -> proc_macro::TokenStream { .enumerate() .map(|(field_index, field)| { if let Some(field_name) = &field.ident { - named_field_method(&field, field_name) + named_field_method(field, field_name) } else { - unnamed_field_method(&field, field_index) + unnamed_field_method(field, field_index) } }); diff --git a/leptos_macro/src/lib.rs b/leptos_macro/src/lib.rs index 4b03a777bf..64849064ce 100644 --- a/leptos_macro/src/lib.rs +++ b/leptos_macro/src/lib.rs @@ -34,6 +34,7 @@ mod view; use view::{client_template::render_template, render_view}; mod component; mod lens; +mod poke; mod server; mod slot; @@ -1008,3 +1009,36 @@ pub fn lens_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { Err(err) => err.to_compile_error().into(), } } + +/// 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::poke; +/// # 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) = poke!(outer_signal.count); +/// +/// let (inner_count, set_inner_count) = poke!(outer_signal.inner.inner_count); +/// let (inner_name, set_inner_name) = poke!(outer_signal.inner.inner_name); +/// ``` +#[proc_macro] +pub fn poke(input: TokenStream) -> TokenStream { + poke::poke_impl(input) +} diff --git a/leptos_macro/src/poke.rs b/leptos_macro/src/poke.rs new file mode 100644 index 0000000000..1c9356f451 --- /dev/null +++ b/leptos_macro/src/poke.rs @@ -0,0 +1,48 @@ +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 PokeMacroInput { + pub root: Ident, + pub path: Punctuated, +} + +impl Parse for PokeMacroInput { + fn parse(input: ParseStream) -> Result { + let root = syn::Ident::parse(input)?; + let _dot = ::parse(input)?; + let path = input.parse_terminated(Type::parse, Token![.])?; + + Ok(PokeMacroInput { root, path }) + } +} + +impl From for TokenStream { + fn from(val: PokeMacroInput) -> 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 poke_impl(tokens: TokenStream) -> TokenStream { + let input = parse_macro_input!(tokens as PokeMacroInput); + input.into() +} diff --git a/leptos_macro/tests/lens.rs b/leptos_macro/tests/lens.rs index 955946c25e..f0e74f5897 100644 --- a/leptos_macro/tests/lens.rs +++ b/leptos_macro/tests/lens.rs @@ -1,5 +1,5 @@ use leptos::{create_runtime, create_rw_signal}; -use leptos_macro::Lens; +use leptos_macro::{poke, Lens}; #[derive(Lens, Default)] struct FieldStruct { @@ -10,6 +10,18 @@ struct FieldStruct { #[derive(Lens, Default)] struct TupleStruct(u32, String); +#[derive(Lens, Default)] +pub struct OuterState { + count: i32, + inner: InnerState, +} + +#[derive(Lens, Clone, PartialEq, Default)] +pub struct InnerState { + inner_count: i32, + inner_name: String, +} + #[test] fn green() { let _ = create_runtime(); @@ -21,6 +33,13 @@ fn green() { let tuple_signal = create_rw_signal(TupleStruct::default()); let _ = TupleStruct::_0(tuple_signal); let _ = TupleStruct::_1(tuple_signal); + + let outer_signal = create_rw_signal(OuterState::default()); + let _ = OuterState::count(outer_signal); + let _ = OuterState::inner(outer_signal); + + let (_, _) = poke!(outer_signal.inner.inner_count); + let (_, _) = poke!(outer_signal.inner.inner_name); } #[test] From ed9ceef9b883d3c29951aed64233b266b3b66fa9 Mon Sep 17 00:00:00 2001 From: SadraMoh Date: Sun, 22 Oct 2023 19:17:31 +0200 Subject: [PATCH 10/10] remove lens derive macro --- leptos_macro/src/lens.rs | 71 ----------------------- leptos_macro/src/lib.rs | 57 +++--------------- leptos_macro/src/{poke.rs => slice.rs} | 25 +++++--- leptos_macro/tests/lens.rs | 49 ---------------- leptos_macro/tests/lens/structures.rs | 17 ------ leptos_macro/tests/lens/structures.stderr | 15 ----- leptos_macro/tests/slice.rs | 32 ++++++++++ leptos_macro/tests/slice/red.rs | 28 +++++++++ leptos_macro/tests/slice/red.stderr | 31 ++++++++++ 9 files changed, 116 insertions(+), 209 deletions(-) delete mode 100644 leptos_macro/src/lens.rs rename leptos_macro/src/{poke.rs => slice.rs} (57%) delete mode 100644 leptos_macro/tests/lens.rs delete mode 100644 leptos_macro/tests/lens/structures.rs delete mode 100644 leptos_macro/tests/lens/structures.stderr create mode 100644 leptos_macro/tests/slice.rs create mode 100644 leptos_macro/tests/slice/red.rs create mode 100644 leptos_macro/tests/slice/red.stderr diff --git a/leptos_macro/src/lens.rs b/leptos_macro/src/lens.rs deleted file mode 100644 index 05329ecf86..0000000000 --- a/leptos_macro/src/lens.rs +++ /dev/null @@ -1,71 +0,0 @@ -use proc_macro2::Ident; -use quote::quote; -use syn::{spanned::Spanned, Data, Field}; - -fn named_field_method(field: &Field, name: &Ident) -> proc_macro2::TokenStream { - let field_accessor = name; - let field_ty = &field.ty; - - quote! { - pub fn #name( - signal: ::leptos::RwSignal, - ) -> (::leptos::Signal<#field_ty>, ::leptos::SignalSetter<#field_ty>) { - ::leptos::create_slice( - signal, - |st: &Self| st.#field_accessor.clone(), - |st: &mut Self, n: #field_ty| st.#field_accessor = n - ) - } - } -} - -fn unnamed_field_method( - field: &Field, - index: usize, -) -> proc_macro2::TokenStream { - let number_ident = syn::Ident::new(&format!("_{index}"), field.span()); - let field_accessor = syn::Index::from(index); - let field_ty = &field.ty; - - quote! { - pub fn #number_ident( - signal: ::leptos::RwSignal, - ) -> (::leptos::Signal<#field_ty>, ::leptos::SignalSetter<#field_ty>) { - ::leptos::create_slice( - signal, - |st: &Self| st.#field_accessor.clone(), - |st: &mut Self, n: #field_ty| st.#field_accessor = n - ) - } - } -} - -pub fn lens_impl(ast: &syn::DeriveInput) -> proc_macro::TokenStream { - let source_ident = &ast.ident; - - let Data::Struct(source_data) = &ast.data else { - panic!("Lens cannot be derived by enums or unions") - }; - - let lens_methods = - source_data - .fields - .iter() - .enumerate() - .map(|(field_index, field)| { - if let Some(field_name) = &field.ident { - named_field_method(field, field_name) - } else { - unnamed_field_method(field, field_index) - } - }); - - quote! { - - impl #source_ident { - #(#lens_methods)* - } - - } - .into() -} diff --git a/leptos_macro/src/lib.rs b/leptos_macro/src/lib.rs index 57abd3192c..0bf4f78454 100644 --- a/leptos_macro/src/lib.rs +++ b/leptos_macro/src/lib.rs @@ -33,9 +33,8 @@ mod params; mod view; use view::{client_template::render_template, render_view}; mod component; -mod lens; -mod poke; mod server; +mod slice; mod slot; /// The `view` macro uses RSX (like JSX, but Rust!) It follows most of the @@ -972,55 +971,13 @@ pub(crate) fn attribute_value(attr: &KeyedAttribute) -> &syn::Expr { } } -/// Generates a function for each field of the deriving struct that calls -/// [create_slice](https://docs.rs/leptos/latest/leptos/fn.create_slice.html) with a default getter and setter. -/// -/// ```rust -/// # use leptos::{create_runtime, create_rw_signal}; -/// # use leptos_macro::Lens; -/// # let runtime = create_runtime(); -/// -/// // some global state with independent fields -/// #[derive(Lens, Default)] -/// pub struct GlobalState { -/// count: i32, -/// name: String, -/// } -/// -/// let state = create_rw_signal(GlobalState::default()); -/// let (count, set_count) = GlobalState::count(state); -/// ``` -/// -/// for tuple structs -/// -/// ```rust -/// # use leptos::{create_runtime, create_rw_signal}; -/// # use leptos_macro::Lens; -/// # let runtime = create_runtime(); -/// -/// // global state in the form of a tuple struct -/// #[derive(Lens, Default)] -/// pub struct GlobalState(i32, String); -/// -/// let state = create_rw_signal(GlobalState::default()); -/// let (number, set_number) = GlobalState::_0(state); -/// let (str, set_str) = GlobalState::_1(state); -/// ``` -#[proc_macro_derive(Lens)] -pub fn lens_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - match syn::parse(input) { - Ok(ast) => lens::lens_impl(&ast), - Err(err) => err.to_compile_error().into(), - } -} - /// 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::poke; +/// # use leptos_macro::slice; /// # let runtime = create_runtime(); /// /// #[derive(Default)] @@ -1037,12 +994,12 @@ pub fn lens_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// /// let outer_signal = create_rw_signal(Outer::default()); /// -/// let (count, set_count) = poke!(outer_signal.count); +/// let (count, set_count) = slice!(outer_signal.count); /// -/// let (inner_count, set_inner_count) = poke!(outer_signal.inner.inner_count); -/// let (inner_name, set_inner_name) = poke!(outer_signal.inner.inner_name); +/// 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 poke(input: TokenStream) -> TokenStream { - poke::poke_impl(input) +pub fn slice(input: TokenStream) -> TokenStream { + slice::slice_impl(input) } diff --git a/leptos_macro/src/poke.rs b/leptos_macro/src/slice.rs similarity index 57% rename from leptos_macro/src/poke.rs rename to leptos_macro/src/slice.rs index 1c9356f451..b609f31c9d 100644 --- a/leptos_macro/src/poke.rs +++ b/leptos_macro/src/slice.rs @@ -11,23 +11,34 @@ use syn::{ Token, Type, }; -struct PokeMacroInput { +struct SliceMacroInput { pub root: Ident, pub path: Punctuated, } -impl Parse for PokeMacroInput { +impl Parse for SliceMacroInput { fn parse(input: ParseStream) -> Result { let root = syn::Ident::parse(input)?; let _dot = ::parse(input)?; let path = input.parse_terminated(Type::parse, Token![.])?; - Ok(PokeMacroInput { root, path }) + 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 for TokenStream { - fn from(val: PokeMacroInput) -> Self { +impl From for TokenStream { + fn from(val: SliceMacroInput) -> Self { let root = val.root; let path = val.path; @@ -42,7 +53,7 @@ impl From for TokenStream { } } -pub fn poke_impl(tokens: TokenStream) -> TokenStream { - let input = parse_macro_input!(tokens as PokeMacroInput); +pub fn slice_impl(tokens: TokenStream) -> TokenStream { + let input = parse_macro_input!(tokens as SliceMacroInput); input.into() } diff --git a/leptos_macro/tests/lens.rs b/leptos_macro/tests/lens.rs deleted file mode 100644 index f0e74f5897..0000000000 --- a/leptos_macro/tests/lens.rs +++ /dev/null @@ -1,49 +0,0 @@ -use leptos::{create_runtime, create_rw_signal}; -use leptos_macro::{poke, Lens}; - -#[derive(Lens, Default)] -struct FieldStruct { - age: u32, - name: String, -} - -#[derive(Lens, Default)] -struct TupleStruct(u32, String); - -#[derive(Lens, Default)] -pub struct OuterState { - count: i32, - inner: InnerState, -} - -#[derive(Lens, Clone, PartialEq, Default)] -pub struct InnerState { - inner_count: i32, - inner_name: String, -} - -#[test] -fn green() { - let _ = create_runtime(); - - let field_signal = create_rw_signal(FieldStruct::default()); - let _ = FieldStruct::age(field_signal); - let _ = FieldStruct::name(field_signal); - - let tuple_signal = create_rw_signal(TupleStruct::default()); - let _ = TupleStruct::_0(tuple_signal); - let _ = TupleStruct::_1(tuple_signal); - - let outer_signal = create_rw_signal(OuterState::default()); - let _ = OuterState::count(outer_signal); - let _ = OuterState::inner(outer_signal); - - let (_, _) = poke!(outer_signal.inner.inner_count); - let (_, _) = poke!(outer_signal.inner.inner_name); -} - -#[test] -fn red() { - let t = trybuild::TestCases::new(); - t.compile_fail("tests/lens/structures.rs") -} diff --git a/leptos_macro/tests/lens/structures.rs b/leptos_macro/tests/lens/structures.rs deleted file mode 100644 index 0c5f637154..0000000000 --- a/leptos_macro/tests/lens/structures.rs +++ /dev/null @@ -1,17 +0,0 @@ -use leptos_macro::Lens; - -#[derive(Lens)] -enum FailOnEnum { - This(u32), - Should(String), - Panic, -} - -#[derive(Lens)] -union FaulOnUnion { - this: u32, - should: std::mem::ManuallyDrop, - panic: std::mem::ManuallyDrop>, -} - -fn main() {} diff --git a/leptos_macro/tests/lens/structures.stderr b/leptos_macro/tests/lens/structures.stderr deleted file mode 100644 index 7dbda38229..0000000000 --- a/leptos_macro/tests/lens/structures.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error: proc-macro derive panicked - --> tests/lens/structures.rs:3:10 - | -3 | #[derive(Lens)] - | ^^^^ - | - = help: message: Lens cannot be derived by enums or unions - -error: proc-macro derive panicked - --> tests/lens/structures.rs:10:10 - | -10 | #[derive(Lens)] - | ^^^^ - | - = help: message: Lens cannot be derived by enums or unions diff --git a/leptos_macro/tests/slice.rs b/leptos_macro/tests/slice.rs new file mode 100644 index 0000000000..4c413c1e78 --- /dev/null +++ b/leptos_macro/tests/slice.rs @@ -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") +} diff --git a/leptos_macro/tests/slice/red.rs b/leptos_macro/tests/slice/red.rs new file mode 100644 index 0000000000..7ced3ab7aa --- /dev/null +++ b/leptos_macro/tests/slice/red.rs @@ -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.); +} diff --git a/leptos_macro/tests/slice/red.stderr b/leptos_macro/tests/slice/red.stderr new file mode 100644 index 0000000000..db4666dfd4 --- /dev/null +++ b/leptos_macro/tests/slice/red.stderr @@ -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)