Allow spread attributes to be set directly (#3881)
* allow spread attributes to be set directly * Remove ordering requirement
This commit is contained in:
1 parent
8c866b783d
commit
589440b9cb
3 files changed
+119
-42
No files matched your search
@@ -33,7 +33,9 @@ pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
|
|||||||
let fields = quote!(#(#fields)*).into_iter();
|
let fields = quote!(#(#fields)*).into_iter();
|
||||||
let required_fields = struct_info
|
let required_fields = struct_info
|
||||||
.included_fields()
|
.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))
|
.map(|f| struct_info.required_field_impl(f))
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
let build_method = struct_info.build_method_impl();
|
let build_method = struct_info.build_method_impl();
|
||||||
@@ -172,7 +174,7 @@ mod util {
|
|||||||
mod field_info {
|
mod field_info {
|
||||||
use crate::props::type_from_inside_option;
|
use crate::props::type_from_inside_option;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::{format_ident, quote};
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::{parse::Error, punctuated::Punctuated};
|
use syn::{parse::Error, punctuated::Punctuated};
|
||||||
use syn::{parse_quote, Expr, Path};
|
use syn::{parse_quote, Expr, Path};
|
||||||
@@ -269,6 +271,13 @@ mod field_info {
|
|||||||
}
|
}
|
||||||
.into()
|
.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)]
|
#[derive(Debug, Default, Clone)]
|
||||||
@@ -566,9 +575,7 @@ mod struct_info {
|
|||||||
|
|
||||||
impl<'a> StructInfo<'a> {
|
impl<'a> StructInfo<'a> {
|
||||||
pub fn included_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {
|
pub fn included_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {
|
||||||
self.fields
|
self.fields.iter().filter(|f| !f.builder_attr.skip)
|
||||||
.iter()
|
|
||||||
.filter(|f| !f.builder_attr.skip && f.builder_attr.extends.is_empty())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extend_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {
|
pub fn extend_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {
|
||||||
@@ -670,7 +677,6 @@ mod struct_info {
|
|||||||
|
|
||||||
let regular_fields: Vec<_> = self
|
let regular_fields: Vec<_> = self
|
||||||
.included_fields()
|
.included_fields()
|
||||||
.chain(self.extend_fields())
|
|
||||||
.filter(|f| !looks_like_signal_type(f.ty) && !looks_like_callback_type(f.ty))
|
.filter(|f| !looks_like_signal_type(f.ty) && !looks_like_callback_type(f.ty))
|
||||||
.map(|f| {
|
.map(|f| {
|
||||||
let name = f.name;
|
let name = f.name;
|
||||||
@@ -840,7 +846,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
let global_fields = self
|
let global_fields = self
|
||||||
.extend_fields()
|
.extend_fields()
|
||||||
.map(|f| {
|
.map(|f| {
|
||||||
let name = f.name;
|
let name = f.extends_vec_ident();
|
||||||
let ty = f.ty;
|
let ty = f.ty;
|
||||||
quote!(#name: #ty)
|
quote!(#name: #ty)
|
||||||
})
|
})
|
||||||
@@ -848,7 +854,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
let global_fields_value = self
|
let global_fields_value = self
|
||||||
.extend_fields()
|
.extend_fields()
|
||||||
.map(|f| {
|
.map(|f| {
|
||||||
let name = f.name;
|
let name = f.extends_vec_ident();
|
||||||
quote!(#name: Vec::new())
|
quote!(#name: Vec::new())
|
||||||
})
|
})
|
||||||
.chain(
|
.chain(
|
||||||
@@ -922,15 +928,11 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
ref builder_name, ..
|
ref builder_name, ..
|
||||||
} = *self;
|
} = *self;
|
||||||
|
|
||||||
let field_name = field.name;
|
let field_name = field.extends_vec_ident().unwrap();
|
||||||
|
|
||||||
let descructuring = self.included_fields().map(|f| {
|
let descructuring = self.included_fields().map(|f| {
|
||||||
if f.ordinal == field.ordinal {
|
let name = f.name;
|
||||||
quote!(_)
|
quote!(#name)
|
||||||
} else {
|
|
||||||
let name = f.name;
|
|
||||||
quote!(#name)
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
let reconstructing = self.included_fields().map(|f| f.name);
|
let reconstructing = self.included_fields().map(|f| f.name);
|
||||||
|
|
||||||
@@ -962,7 +964,12 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
.count();
|
.count();
|
||||||
for f in self.included_fields() {
|
for f in self.included_fields() {
|
||||||
if f.ordinal == field.ordinal {
|
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
|
target_generics_tuple
|
||||||
.elems
|
.elems
|
||||||
.push_value(f.tuplized_type_ty_param());
|
.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 (impl_generics, _, where_clause) = generics.split_for_impl();
|
||||||
|
|
||||||
let forward_extended_fields = self.extend_fields().map(|f| {
|
let forward_extended_fields = self.extend_fields().map(|f| {
|
||||||
let name = f.name;
|
let name = f.extends_vec_ident();
|
||||||
quote!(#name: self.#name)
|
quote!(#name: self.#name)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1176,7 +1183,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
let forward_fields = self
|
let forward_fields = self
|
||||||
.extend_fields()
|
.extend_fields()
|
||||||
.map(|f| {
|
.map(|f| {
|
||||||
let name = f.name;
|
let name = f.extends_vec_ident();
|
||||||
quote!(#name: self.#name)
|
quote!(#name: self.#name)
|
||||||
})
|
})
|
||||||
.chain(
|
.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 {
|
pub fn build_method_impl(&self) -> TokenStream {
|
||||||
let StructInfo {
|
let StructInfo {
|
||||||
ref name,
|
ref name,
|
||||||
@@ -1338,27 +1370,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
.count();
|
.count();
|
||||||
for field in self.included_fields() {
|
for field in self.included_fields() {
|
||||||
if field.builder_attr.default.is_some() {
|
if field.builder_attr.default.is_some() {
|
||||||
let trait_ref = syn::TraitBound {
|
let generic_param = self.generic_builder_param(field);
|
||||||
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());
|
|
||||||
g.params
|
g.params
|
||||||
.insert(index_after_lifetime_in_generics, generic_param.into());
|
.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.
|
// reordering based on that, but for now this much simpler thing is a reasonable approach.
|
||||||
let assignments = self.fields.iter().map(|field| {
|
let assignments = self.fields.iter().map(|field| {
|
||||||
let name = &field.name;
|
let name = &field.name;
|
||||||
if !field.builder_attr.extends.is_empty() {
|
if let Some(extends_vec) = field.extends_vec_ident() {
|
||||||
quote!(let #name = self.#name;)
|
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 {
|
} else if let Some(ref default) = field.builder_attr.default {
|
||||||
|
|
||||||
// If field has `into`, apply it to the default value.
|
// If field has `into`, apply it to the default value.
|
||||||
// Ignore any blank defaults as it causes type inference errors.
|
// Ignore any blank defaults as it causes type inference errors.
|
||||||
let is_default = *default == parse_quote!(::core::default::Default::default());
|
let is_default = *default == parse_quote!(::core::default::Default::default());
|
||||||
|
|||||||
@@ -2,6 +2,14 @@ use super::*;
|
|||||||
use crate::document;
|
use crate::document;
|
||||||
use dioxus_html as dioxus_elements;
|
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]
|
#[non_exhaustive]
|
||||||
#[derive(Clone, Props, PartialEq)]
|
#[derive(Clone, Props, PartialEq)]
|
||||||
pub struct LinkProps {
|
pub struct LinkProps {
|
||||||
|
|||||||
@@ -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>"#
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user