From fd6070c39eced5c0562c73717c5786bec827b84b Mon Sep 17 00:00:00 2001 From: Georg Vienna Date: Wed, 18 Dec 2024 11:33:27 +0100 Subject: [PATCH] feat: allow to destructure props --- leptos_macro/src/component.rs | 47 ++++++++++++++--- leptos_macro/tests/component.rs | 50 +++++++++++++++++++ leptos_macro/tests/ui/component.rs | 6 +++ leptos_macro/tests/ui/component.stderr | 14 ++++-- .../tests/ui/component_absolute.stderr | 8 +-- 5 files changed, 109 insertions(+), 16 deletions(-) diff --git a/leptos_macro/src/component.rs b/leptos_macro/src/component.rs index 37d6d42a74..4a3bf80595 100644 --- a/leptos_macro/src/component.rs +++ b/leptos_macro/src/component.rs @@ -655,14 +655,44 @@ impl Prop { abort!(e.span(), e.to_string()); }); - let name = if let Pat::Ident(i) = *typed.pat { - i - } else { - abort!( - typed.pat, - "only `prop: bool` style types are allowed within the \ - `#[component]` macro" - ); + let name = match *typed.pat { + Pat::Ident(i) => { + if let Some(name) = &prop_opts.name { + PatIdent { + attrs: vec![], + by_ref: None, + mutability: None, + ident: Ident::new(name, i.span()), + subpat: None, + } + } else { + i + } + } + Pat::Struct(_) | Pat::Tuple(_) | Pat::TupleStruct(_) => { + if let Some(name) = &prop_opts.name { + PatIdent { + attrs: vec![], + by_ref: None, + mutability: None, + ident: Ident::new(name, typed.pat.span()), + subpat: None, + } + } else { + abort!( + typed.pat, + "destructured props must be given a name e.g. \ + #[prop(name = \"data\")]" + ); + } + } + _ => { + abort!( + typed.pat, + "only `prop: bool` style types are allowed within the \ + `#[component]` macro" + ); + } }; Self { @@ -865,6 +895,7 @@ struct PropOpt { default: Option, into: bool, attrs: bool, + name: Option, } struct TypedBuilderOpts { diff --git a/leptos_macro/tests/component.rs b/leptos_macro/tests/component.rs index 2bd2977188..d90dbeeefd 100644 --- a/leptos_macro/tests/component.rs +++ b/leptos_macro/tests/component.rs @@ -1,6 +1,15 @@ use core::num::NonZeroUsize; use leptos::prelude::*; +#[derive(PartialEq, Debug)] +struct UserInfo { + user_id: String, + email: String, +} + +#[derive(PartialEq, Debug)] +struct Admin(bool); + #[component] fn Component( #[prop(optional)] optional: bool, @@ -10,6 +19,10 @@ fn Component( #[prop(default = NonZeroUsize::new(10).unwrap())] default: NonZeroUsize, #[prop(into)] into: String, impl_trait: impl Fn() -> i32 + 'static, + #[prop(name = "data")] UserInfo { email, user_id }: UserInfo, + #[prop(name = "tuple")] (name, id): (String, i32), + #[prop(name = "tuple_struct")] Admin(is_admin): Admin, + #[prop(name = "outside_name")] inside_name: i32, ) -> impl IntoView { _ = optional; _ = optional_into; @@ -18,6 +31,12 @@ fn Component( _ = default; _ = into; _ = impl_trait; + _ = email; + _ = user_id; + _ = id; + _ = name; + _ = is_admin; + _ = inside_name; } #[test] @@ -26,6 +45,13 @@ fn component() { .into("") .strip_option(9) .impl_trait(|| 42) + .data(UserInfo { + email: "em@il".into(), + user_id: "1".into(), + }) + .tuple(("Joe".into(), 12)) + .tuple_struct(Admin(true)) + .outside_name(1) .build(); assert!(!cp.optional); assert_eq!(cp.optional_into, None); @@ -34,6 +60,16 @@ fn component() { assert_eq!(cp.default, NonZeroUsize::new(10).unwrap()); assert_eq!(cp.into, ""); assert_eq!((cp.impl_trait)(), 42); + assert_eq!( + cp.data, + UserInfo { + email: "em@il".into(), + user_id: "1".into(), + } + ); + assert_eq!(cp.tuple, ("Joe".into(), 12)); + assert_eq!(cp.tuple_struct, Admin(true)); + assert_eq!(cp.outside_name, 1); } #[test] @@ -45,12 +81,26 @@ fn component_nostrip() { strip_option=9 into="" impl_trait=|| 42 + data=UserInfo { + email: "em@il".into(), + user_id: "1".into(), + } + tuple=("Joe".into(), 12) + tuple_struct=Admin(true) + outside_name=1 /> }; } diff --git a/leptos_macro/tests/ui/component.rs b/leptos_macro/tests/ui/component.rs index f1951861db..3743857316 100644 --- a/leptos_macro/tests/ui/component.rs +++ b/leptos_macro/tests/ui/component.rs @@ -44,4 +44,10 @@ fn default_with_invalid_value( _ = default; } +#[component] +fn destructure_without_name((default, value): (bool, i32)) -> impl IntoView { + _ = default; + _ = value; +} + fn main() {} diff --git a/leptos_macro/tests/ui/component.stderr b/leptos_macro/tests/ui/component.stderr index fd1fe019f7..3c2ecaa52d 100644 --- a/leptos_macro/tests/ui/component.stderr +++ b/leptos_macro/tests/ui/component.stderr @@ -1,4 +1,4 @@ -error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into` and `attrs` +error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into`, `attrs` and `name` --> tests/ui/component.rs:10:31 | 10 | fn unknown_prop_option(#[prop(hello)] test: bool) -> impl IntoView { @@ -8,19 +8,19 @@ error: `optional` conflicts with mutually exclusive `optional_no_strip` --> tests/ui/component.rs:16:12 | 16 | #[prop(optional, optional_no_strip)] conflicting: bool, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^ error: `optional` conflicts with mutually exclusive `strip_option` --> tests/ui/component.rs:23:12 | 23 | #[prop(optional, strip_option)] conflicting: bool, - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^ error: `optional_no_strip` conflicts with mutually exclusive `strip_option` --> tests/ui/component.rs:30:12 | 30 | #[prop(optional_no_strip, strip_option)] conflicting: bool, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^ error: unexpected end of input, expected `=` or `(` @@ -41,3 +41,9 @@ error: unexpected end of input, expected one of: identifier, `::`, `<`, `_`, lit | ^^^^^^^^^^^^ | = note: this error originates in the attribute macro `component` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: destructured props must be given a name e.g. #[prop(name = "data")] + --> tests/ui/component.rs:48:29 + | +48 | fn destructure_without_name((default, value): (bool, i32)) -> impl IntoView { + | ^^^^^^^^^^^^^^^^ diff --git a/leptos_macro/tests/ui/component_absolute.stderr b/leptos_macro/tests/ui/component_absolute.stderr index fe08cf308a..e7a6188358 100644 --- a/leptos_macro/tests/ui/component_absolute.stderr +++ b/leptos_macro/tests/ui/component_absolute.stderr @@ -1,4 +1,4 @@ -error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into` and `attrs` +error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into`, `attrs` and `name` --> tests/ui/component_absolute.rs:5:31 | 5 | fn unknown_prop_option(#[prop(hello)] test: bool) -> impl ::leptos::IntoView { @@ -8,19 +8,19 @@ error: `optional` conflicts with mutually exclusive `optional_no_strip` --> tests/ui/component_absolute.rs:11:12 | 11 | #[prop(optional, optional_no_strip)] conflicting: bool, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^ error: `optional` conflicts with mutually exclusive `strip_option` --> tests/ui/component_absolute.rs:18:12 | 18 | #[prop(optional, strip_option)] conflicting: bool, - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^ error: `optional_no_strip` conflicts with mutually exclusive `strip_option` --> tests/ui/component_absolute.rs:25:12 | 25 | #[prop(optional_no_strip, strip_option)] conflicting: bool, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^ error: unexpected end of input, expected `=` or `(`