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 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());
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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