Browse Source

Allow spread attributes to be set directly (#3881)

* allow spread attributes to be set directly

* Remove ordering requirement
Evan Almloff 2 tháng trước cách đây
mục cha
commit
589440b9cb

+ 56 - 42
packages/core-macro/src/props/mod.rs

@@ -33,7 +33,9 @@ pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
                 let fields = quote!(#(#fields)*).into_iter();
                 let required_fields = struct_info
                     .included_fields()
-                    .filter(|f| f.builder_attr.default.is_none())
+                    .filter(|f| {
+                        f.builder_attr.default.is_none() && f.builder_attr.extends.is_empty()
+                    })
                     .map(|f| struct_info.required_field_impl(f))
                     .collect::<Result<Vec<_>, _>>()?;
                 let build_method = struct_info.build_method_impl();
@@ -172,7 +174,7 @@ mod util {
 mod field_info {
     use crate::props::type_from_inside_option;
     use proc_macro2::TokenStream;
-    use quote::quote;
+    use quote::{format_ident, quote};
     use syn::spanned::Spanned;
     use syn::{parse::Error, punctuated::Punctuated};
     use syn::{parse_quote, Expr, Path};
@@ -269,6 +271,13 @@ mod field_info {
             }
             .into()
         }
+
+        pub fn extends_vec_ident(&self) -> Option<syn::Ident> {
+            (!self.builder_attr.extends.is_empty()).then(|| {
+                let ident = &self.name;
+                format_ident!("__{ident}_attributes")
+            })
+        }
     }
 
     #[derive(Debug, Default, Clone)]
@@ -566,9 +575,7 @@ mod struct_info {
 
     impl<'a> StructInfo<'a> {
         pub fn included_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {
-            self.fields
-                .iter()
-                .filter(|f| !f.builder_attr.skip && f.builder_attr.extends.is_empty())
+            self.fields.iter().filter(|f| !f.builder_attr.skip)
         }
 
         pub fn extend_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {
@@ -670,7 +677,6 @@ mod struct_info {
 
             let regular_fields: Vec<_> = self
                 .included_fields()
-                .chain(self.extend_fields())
                 .filter(|f| !looks_like_signal_type(f.ty) && !looks_like_callback_type(f.ty))
                 .map(|f| {
                     let name = f.name;
@@ -840,7 +846,7 @@ Finally, call `.build()` to create the instance of `{name}`.
             let global_fields = self
                 .extend_fields()
                 .map(|f| {
-                    let name = f.name;
+                    let name = f.extends_vec_ident();
                     let ty = f.ty;
                     quote!(#name: #ty)
                 })
@@ -848,7 +854,7 @@ Finally, call `.build()` to create the instance of `{name}`.
             let global_fields_value = self
                 .extend_fields()
                 .map(|f| {
-                    let name = f.name;
+                    let name = f.extends_vec_ident();
                     quote!(#name: Vec::new())
                 })
                 .chain(
@@ -922,15 +928,11 @@ Finally, call `.build()` to create the instance of `{name}`.
                 ref builder_name, ..
             } = *self;
 
-            let field_name = field.name;
+            let field_name = field.extends_vec_ident().unwrap();
 
             let descructuring = self.included_fields().map(|f| {
-                if f.ordinal == field.ordinal {
-                    quote!(_)
-                } else {
-                    let name = f.name;
-                    quote!(#name)
-                }
+                let name = f.name;
+                quote!(#name)
             });
             let reconstructing = self.included_fields().map(|f| f.name);
 
@@ -962,7 +964,12 @@ Finally, call `.build()` to create the instance of `{name}`.
                     .count();
                 for f in self.included_fields() {
                     if f.ordinal == field.ordinal {
-                        ty_generics_tuple.elems.push_value(empty_type());
+                        g.params.insert(
+                            index_after_lifetime_in_generics,
+                            syn::GenericParam::Type(self.generic_builder_param(f)),
+                        );
+                        let generic_argument: syn::Type = f.type_ident();
+                        ty_generics_tuple.elems.push_value(generic_argument.clone());
                         target_generics_tuple
                             .elems
                             .push_value(f.tuplized_type_ty_param());
@@ -993,7 +1000,7 @@ Finally, call `.build()` to create the instance of `{name}`.
             let (impl_generics, _, where_clause) = generics.split_for_impl();
 
             let forward_extended_fields = self.extend_fields().map(|f| {
-                let name = f.name;
+                let name = f.extends_vec_ident();
                 quote!(#name: self.#name)
             });
 
@@ -1176,7 +1183,7 @@ Finally, call `.build()` to create the instance of `{name}`.
             let forward_fields = self
                 .extend_fields()
                 .map(|f| {
-                    let name = f.name;
+                    let name = f.extends_vec_ident();
                     quote!(#name: self.#name)
                 })
                 .chain(
@@ -1323,6 +1330,31 @@ Finally, call `.build()` to create the instance of `{name}`.
             })
         }
 
+        fn generic_builder_param(&self, field: &FieldInfo) -> syn::TypeParam {
+            let trait_ref = syn::TraitBound {
+                paren_token: None,
+                lifetimes: None,
+                modifier: syn::TraitBoundModifier::None,
+                path: syn::PathSegment {
+                    ident: self.conversion_helper_trait_name.clone(),
+                    arguments: syn::PathArguments::AngleBracketed(
+                        syn::AngleBracketedGenericArguments {
+                            colon2_token: None,
+                            lt_token: Default::default(),
+                            args: make_punctuated_single(syn::GenericArgument::Type(
+                                field.ty.clone(),
+                            )),
+                            gt_token: Default::default(),
+                        },
+                    ),
+                }
+                .into(),
+            };
+            let mut generic_param: syn::TypeParam = field.generic_ident.clone().into();
+            generic_param.bounds.push(trait_ref.into());
+            generic_param
+        }
+
         pub fn build_method_impl(&self) -> TokenStream {
             let StructInfo {
                 ref name,
@@ -1338,27 +1370,7 @@ Finally, call `.build()` to create the instance of `{name}`.
                     .count();
                 for field in self.included_fields() {
                     if field.builder_attr.default.is_some() {
-                        let trait_ref = syn::TraitBound {
-                            paren_token: None,
-                            lifetimes: None,
-                            modifier: syn::TraitBoundModifier::None,
-                            path: syn::PathSegment {
-                                ident: self.conversion_helper_trait_name.clone(),
-                                arguments: syn::PathArguments::AngleBracketed(
-                                    syn::AngleBracketedGenericArguments {
-                                        colon2_token: None,
-                                        lt_token: Default::default(),
-                                        args: make_punctuated_single(syn::GenericArgument::Type(
-                                            field.ty.clone(),
-                                        )),
-                                        gt_token: Default::default(),
-                                    },
-                                ),
-                            }
-                            .into(),
-                        };
-                        let mut generic_param: syn::TypeParam = field.generic_ident.clone().into();
-                        generic_param.bounds.push(trait_ref.into());
+                        let generic_param = self.generic_builder_param(field);
                         g.params
                             .insert(index_after_lifetime_in_generics, generic_param.into());
                     }
@@ -1395,10 +1407,12 @@ Finally, call `.build()` to create the instance of `{name}`.
             // reordering based on that, but for now this much simpler thing is a reasonable approach.
             let assignments = self.fields.iter().map(|field| {
                 let name = &field.name;
-                if !field.builder_attr.extends.is_empty() {
-                    quote!(let #name = self.#name;)
+                if let Some(extends_vec) = field.extends_vec_ident() {
+                    quote!{
+                        let mut #name = #helper_trait_name::into_value(#name, || ::core::default::Default::default());
+                        #name.extend(self.#extends_vec);
+                    }
                 } else if let Some(ref default) = field.builder_attr.default {
-
                     // If field has `into`, apply it to the default value.
                     // Ignore any blank defaults as it causes type inference errors.
                     let is_default = *default == parse_quote!(::core::default::Default::default());

+ 8 - 0
packages/document/src/elements/link.rs

@@ -2,6 +2,14 @@ use super::*;
 use crate::document;
 use dioxus_html as dioxus_elements;
 
+#[non_exhaustive]
+#[derive(Clone, Props, PartialEq)]
+pub struct OtherLinkProps {
+    pub rel: String,
+    #[props(extends = link, extends = GlobalAttributes)]
+    pub additional_attributes: Vec<Attribute>,
+}
+
 #[non_exhaustive]
 #[derive(Clone, Props, PartialEq)]
 pub struct LinkProps {

+ 55 - 0
packages/ssr/tests/forward_spreads.rs

@@ -0,0 +1,55 @@
+use dioxus::prelude::*;
+
+// Regression test for https://github.com/DioxusLabs/dioxus/issues/3844
+#[test]
+fn forward_spreads() {
+    #[derive(Props, Clone, PartialEq)]
+    struct Comp1Props {
+        #[props(extends = GlobalAttributes)]
+        attributes: Vec<Attribute>,
+    }
+
+    #[component]
+    fn Comp1(props: Comp1Props) -> Element {
+        rsx! {
+            Comp2 {
+                attributes: props.attributes.clone(),
+                height: "100%",
+            }
+            Comp2 {
+                height: "100%",
+                attributes: props.attributes.clone(),
+            }
+        }
+    }
+
+    #[derive(Props, Clone, PartialEq)]
+    struct CompProps2 {
+        #[props(extends = GlobalAttributes)]
+        attributes: Vec<Attribute>,
+    }
+
+    #[component]
+    fn Comp2(props: CompProps2) -> Element {
+        let attributes = props.attributes;
+        rsx! {
+            div {
+                ..attributes
+            }
+        }
+    }
+
+    let merged = || {
+        rsx! {
+            Comp1 {
+                width: "100%"
+            }
+        }
+    };
+    let dom = VirtualDom::prebuilt(merged);
+    let html = dioxus_ssr::render(&dom);
+    assert_eq!(
+        html,
+        r#"<div style="width:100%;height:100%;"></div><div style="width:100%;height:100%;"></div>"#
+    );
+}