Răsfoiți Sursa

WIP: add extends to the props macro

Evan Almloff 1 an în urmă
părinte
comite
7b51bb8060

+ 13 - 56
examples/spread.rs

@@ -1,3 +1,4 @@
+use crate::dioxus_elements::ExtendedDivMarker;
 use dioxus::{
     core::{exports::bumpalo::Bump, Attribute, HasAttributesBox},
     html::{ExtendedGlobalAttributesMarker, GlobalAttributesExtension},
@@ -13,65 +14,15 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    cx.render(::dioxus::core::LazyNodes::new(
-        move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode {
-            static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
-                name: "src/main.rs:15:15:289",
-                roots: &[::dioxus::core::TemplateNode::Dynamic { id: 0usize }],
-                node_paths: &[&[0u8]],
-                attr_paths: &[],
-            };
-            ::dioxus::core::VNode {
-                parent: None,
-                key: None,
-                template: std::cell::Cell::new(TEMPLATE),
-                root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(
-                    1usize,
-                    __cx.bump(),
-                )
-                .into(),
-                dynamic_nodes: __cx.bump().alloc([__cx.component(
-                    Component,
-                    Props {
-                        bump: __cx.bump(),
-                        attributes: Vec::new(),
-                    }
-                    .width(10)
-                    .height("100px"),
-                    "Component",
-                )]),
-                dynamic_attrs: __cx.bump().alloc([]),
-            }
-        },
-    ))
-}
-
-#[derive(Props)]
-struct Props<'a> {
-    bump: &'a Bump,
-    attributes: Vec<Attribute<'a>>,
-}
-
-impl<'a> HasAttributesBox<'a, Props<'a>> for Props<'a> {
-    fn push_attribute(
-        mut self,
-        name: &'a str,
-        ns: Option<&'static str>,
-        attr: impl IntoAttributeValue<'a>,
-        volatile: bool,
-    ) -> Self {
-        self.attributes.push(Attribute {
-            name,
-            namespace: ns,
-            value: attr.into_value(self.bump),
-            volatile,
-        });
-        self
+    render! {
+        Component {
+            width: "10px",
+            height: "10px",
+            left: 1,
+        }
     }
 }
 
-impl ExtendedGlobalAttributesMarker for Props<'_> {}
-
 fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
     let attributes = &*cx.props.attributes;
     render! {
@@ -80,3 +31,9 @@ fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
         }
     }
 }
+
+#[derive(Props)]
+struct Props<'a> {
+    #[props(extends = GlobalAttributes)]
+    attributes: Vec<Attribute<'a>>,
+}

+ 1 - 0
packages/core-macro/Cargo.toml

@@ -19,6 +19,7 @@ syn = { version = "2.0", features = ["full", "extra-traits"] }
 dioxus-rsx = { workspace = true }
 dioxus-core = { workspace = true }
 constcat = "0.3.0"
+convert_case = "^0.6.0"
 
 # testing
 [dev-dependencies]

+ 249 - 89
packages/core-macro/src/props/mod.rs

@@ -24,6 +24,10 @@ pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
                     .included_fields()
                     .map(|f| struct_info.field_impl(f))
                     .collect::<Result<Vec<_>, _>>()?;
+                let extends = struct_info
+                    .extend_fields()
+                    .map(|f| struct_info.extends_impl(f))
+                    .collect::<Result<Vec<_>, _>>()?;
                 let fields = quote!(#(#fields)*).into_iter();
                 let required_fields = struct_info
                     .included_fields()
@@ -36,6 +40,7 @@ pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
                     #builder_creation
                     #conversion_helper
                     #( #fields )*
+                    #( #extends )*
                     #( #required_fields )*
                     #build_method
                 }
@@ -167,8 +172,8 @@ mod field_info {
     use proc_macro2::TokenStream;
     use quote::quote;
     use syn::spanned::Spanned;
-    use syn::Expr;
     use syn::{parse::Error, punctuated::Punctuated};
+    use syn::{Expr, Path};
 
     use super::util::{
         expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix,
@@ -199,6 +204,13 @@ mod field_info {
                     );
                 }
 
+                // extended field is automatically empty
+                if !builder_attr.extends.is_empty() {
+                    builder_attr.default = Some(
+                        syn::parse(quote!(::core::default::Default::default()).into()).unwrap(),
+                    );
+                }
+
                 // auto detect optional
                 let strip_option_auto = builder_attr.strip_option
                     || !builder_attr.ignore_option
@@ -257,6 +269,7 @@ mod field_info {
         pub auto_into: bool,
         pub strip_option: bool,
         pub ignore_option: bool,
+        pub extends: Vec<Path>,
     }
 
     impl FieldBuilderAttr {
@@ -309,6 +322,17 @@ mod field_info {
                     let name = expr_to_single_string(&assign.left)
                         .ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;
                     match name.as_str() {
+                        "extends" => {
+                            if let syn::Expr::Path(path) = *assign.right {
+                                self.extends.push(path.path);
+                                Ok(())
+                            } else {
+                                Err(Error::new_spanned(
+                                    assign.right,
+                                    "Expected simple identifier",
+                                ))
+                            }
+                        }
                         "default" => {
                             self.default = Some(*assign.right);
                             Ok(())
@@ -363,6 +387,11 @@ mod field_info {
                             Ok(())
                         }
 
+                        "extend" => {
+                            self.extends.push(path.path);
+                            Ok(())
+                        }
+
                         _ => {
                             macro_rules! handle_fields {
                                 ( $( $flag:expr, $field:ident, $already:expr; )* ) => {
@@ -466,11 +495,14 @@ fn type_from_inside_option(ty: &syn::Type, check_option_name: bool) -> Option<&s
 }
 
 mod struct_info {
+    use convert_case::{Case, Casing};
     use proc_macro2::TokenStream;
     use quote::quote;
     use syn::parse::Error;
     use syn::punctuated::Punctuated;
-    use syn::Expr;
+    use syn::spanned::Spanned;
+    use syn::visit::Visit;
+    use syn::{Expr, Ident};
 
     use super::field_info::{FieldBuilderAttr, FieldInfo};
     use super::util::{
@@ -493,7 +525,46 @@ 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)
+            self.fields
+                .iter()
+                .filter(|f| !f.builder_attr.skip && f.builder_attr.extends.is_empty())
+        }
+
+        pub fn extend_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {
+            self.fields
+                .iter()
+                .filter(|f| !f.builder_attr.extends.is_empty())
+        }
+
+        fn extend_lifetime(&self) -> syn::Result<Option<syn::Lifetime>> {
+            let first_extend = self.extend_fields().next();
+
+            match first_extend {
+                Some(f) => {
+                    struct VisitFirstLifetime(Option<syn::Lifetime>);
+
+                    impl Visit<'_> for VisitFirstLifetime {
+                        fn visit_lifetime(&mut self, lifetime: &'_ syn::Lifetime) {
+                            if self.0.is_none() {
+                                self.0 = Some(lifetime.clone());
+                            }
+                        }
+                    }
+
+                    let name = f.name;
+                    let mut visitor = VisitFirstLifetime(None);
+
+                    visitor.visit_type(&f.ty);
+
+                    visitor.0.ok_or_else(|| {
+                        syn::Error::new_spanned(
+                            name,
+                            "Unable to find lifetime for extended field. Please specify it manually",
+                        )
+                    }).map(Some)
+                }
+                None => Ok(None),
+            }
         }
 
         pub fn new(
@@ -542,29 +613,12 @@ mod struct_info {
 
             let generics = self.generics.clone();
             let (_, ty_generics, where_clause) = generics.split_for_impl();
-            let impl_generics = self.modify_generics(|g| {
-                // Add a bump lifetime to the generics
-                g.params.insert(
-                    0,
-                    syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new(
-                        "'__bump",
-                        proc_macro2::Span::call_site(),
-                    ))),
-                );
-            });
+            let impl_generics = self.generics.clone();
             let (impl_generics, b_initial_generics, _) = impl_generics.split_for_impl();
             let all_fields_param = syn::GenericParam::Type(
                 syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(),
             );
             let b_generics = self.modify_generics(|g| {
-                // Add a bump lifetime to the generics
-                g.params.insert(
-                    0,
-                    syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new(
-                        "'__bump",
-                        proc_macro2::Span::call_site(),
-                    ))),
-                );
                 g.params.insert(0, all_fields_param.clone());
             });
             let empties_tuple = type_tuple(self.included_fields().map(|_| empty_type()));
@@ -629,8 +683,7 @@ Finally, call `.build()` to create the instance of `{name}`.
                 quote!(#[doc(hidden)])
             };
 
-            let (b_generics_impl, b_generics_ty, b_generics_where_extras_predicates) =
-                b_generics.split_for_impl();
+            let (_, _, b_generics_where_extras_predicates) = b_generics.split_for_impl();
             let mut b_generics_where: syn::WhereClause = syn::parse2(quote! {
                 where TypedBuilderFields: Clone
             })?;
@@ -650,12 +703,26 @@ Finally, call `.build()` to create the instance of `{name}`.
                 false => quote! { true },
             };
 
+            let extend_fields = self.extend_fields().map(|f| {
+                let name = f.name;
+                let ty = f.ty;
+                quote!(#name: #ty)
+            });
+            let extend_fields_value = self.extend_fields().map(|f| {
+                let name = f.name;
+                quote!(#name: Vec::new())
+            });
+            let extend_lifetime = self
+                .extend_lifetime()?
+                .unwrap_or(syn::Lifetime::new("'_", proc_macro2::Span::call_site()));
+
             Ok(quote! {
                 impl #impl_generics #name #ty_generics #where_clause {
                     #[doc = #builder_method_doc]
                     #[allow(dead_code)]
-                    #vis fn builder(_cx: &'__bump ::dioxus::prelude::ScopeState) -> #builder_name #generics_with_empty {
+                    #vis fn builder(_cx: & #extend_lifetime ::dioxus::prelude::ScopeState) -> #builder_name #generics_with_empty {
                         #builder_name {
+                            #(#extend_fields_value,)*
                             bump: _cx.bump(),
                             fields: #empties_tuple,
                             _phantom: ::core::default::Default::default(),
@@ -667,27 +734,18 @@ Finally, call `.build()` to create the instance of `{name}`.
                 #builder_type_doc
                 #[allow(dead_code, non_camel_case_types, non_snake_case)]
                 #vis struct #builder_name #b_generics {
-                    bump: &'__bump ::dioxus::core::exports::bumpalo::Bump,
+                    #(#extend_fields,)*
+                    bump: & #extend_lifetime ::dioxus::core::exports::bumpalo::Bump,
                     fields: #all_fields_param,
                     _phantom: (#( #phantom_generics ),*),
                 }
 
-                impl #b_generics_impl Clone for #builder_name #b_generics_ty #b_generics_where {
-                    fn clone(&self) -> Self {
-                        Self {
-                            bump: self.bump,
-                            fields: self.fields.clone(),
-                            _phantom: ::core::default::Default::default(),
-                        }
-                    }
-                }
-
-                impl #impl_generics ::dioxus::prelude::Properties<'__bump> for #name #ty_generics
+                impl #impl_generics ::dioxus::prelude::Properties<#extend_lifetime> for #name #ty_generics
                 #b_generics_where_extras_predicates
                 {
                     type Builder = #builder_name #generics_with_empty;
                     const IS_STATIC: bool = #is_static;
-                    fn builder(_cx: &'__bump ::dioxus::prelude::ScopeState) -> Self::Builder {
+                    fn builder(_cx: &#extend_lifetime ::dioxus::prelude::ScopeState) -> Self::Builder {
                         #name::builder(_cx)
                     }
                     unsafe fn memoize(&self, other: &Self) -> bool {
@@ -723,6 +781,137 @@ Finally, call `.build()` to create the instance of `{name}`.
             })
         }
 
+        pub fn extends_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {
+            let StructInfo {
+                ref builder_name, ..
+            } = *self;
+
+            let field_name = field.name;
+
+            let descructuring = self.included_fields().map(|f| {
+                if f.ordinal == field.ordinal {
+                    quote!(_)
+                } else {
+                    let name = f.name;
+                    quote!(#name)
+                }
+            });
+            let reconstructing = self.included_fields().map(|f| f.name);
+
+            // Add the bump lifetime to the generics
+            let mut ty_generics: Vec<syn::GenericArgument> = self
+                .generics
+                .params
+                .iter()
+                .map(|generic_param| match generic_param {
+                    syn::GenericParam::Type(type_param) => {
+                        let ident = type_param.ident.clone();
+                        syn::parse(quote!(#ident).into()).unwrap()
+                    }
+                    syn::GenericParam::Lifetime(lifetime_def) => {
+                        syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone())
+                    }
+                    syn::GenericParam::Const(const_param) => {
+                        let ident = const_param.ident.clone();
+                        syn::parse(quote!(#ident).into()).unwrap()
+                    }
+                })
+                .collect();
+            let mut target_generics_tuple = empty_type_tuple();
+            let mut ty_generics_tuple = empty_type_tuple();
+            let generics = self.modify_generics(|g| {
+                let index_after_lifetime_in_generics = g
+                    .params
+                    .iter()
+                    .filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))
+                    .count();
+                for f in self.included_fields() {
+                    if f.ordinal == field.ordinal {
+                        ty_generics_tuple.elems.push_value(empty_type());
+                        target_generics_tuple
+                            .elems
+                            .push_value(f.tuplized_type_ty_param());
+                    } else {
+                        g.params
+                            .insert(index_after_lifetime_in_generics, f.generic_ty_param());
+                        let generic_argument: syn::Type = f.type_ident();
+                        ty_generics_tuple.elems.push_value(generic_argument.clone());
+                        target_generics_tuple.elems.push_value(generic_argument);
+                    }
+                    ty_generics_tuple.elems.push_punct(Default::default());
+                    target_generics_tuple.elems.push_punct(Default::default());
+                }
+            });
+            let mut target_generics = ty_generics.clone();
+            let index_after_lifetime_in_generics = target_generics
+                .iter()
+                .filter(|arg| matches!(arg, syn::GenericArgument::Lifetime(_)))
+                .count();
+            target_generics.insert(
+                index_after_lifetime_in_generics,
+                syn::GenericArgument::Type(target_generics_tuple.into()),
+            );
+            ty_generics.insert(
+                index_after_lifetime_in_generics,
+                syn::GenericArgument::Type(ty_generics_tuple.into()),
+            );
+            let (impl_generics, _, where_clause) = generics.split_for_impl();
+
+            let forward_extended_fields = self.extend_fields().map(|f| {
+                let name = f.name;
+                quote!(#name: self.#name)
+            });
+
+            let extends_impl = field.builder_attr.extends.iter().map(|path| {
+                let name_str = path_to_single_string(path).unwrap();
+                let camel_name = name_str.to_case(Case::UpperCamel);
+                let marker_name = Ident::new(
+                    format!("Extended{}Marker", &camel_name).as_str(),
+                    path.span(),
+                );
+                quote! {
+                    impl #impl_generics #marker_name for #builder_name < #( #ty_generics ),* > #where_clause {}
+                }
+            });
+            let extend_lifetime = self.extend_lifetime()?.ok_or(Error::new_spanned(
+                field_name,
+                "Unable to find lifetime for extended field. Please specify it manually",
+            ))?;
+
+            Ok(quote! {
+                impl #impl_generics ::dioxus::prelude::HasAttributesBox<#extend_lifetime> for #builder_name < #( #ty_generics ),* > #where_clause {
+                    fn push_attribute(
+                        mut self,
+                        name: &#extend_lifetime str,
+                        ns: Option<&'static str>,
+                        attr: impl ::dioxus::prelude::IntoAttributeValue<#extend_lifetime>,
+                        volatile: bool
+                    ) -> Self {
+                        let ( #(#descructuring,)* ) = self.fields;
+                        self.#field_name.push(
+                            ::dioxus::core::Attribute::new(
+                                name,
+                                {
+                                    use ::dioxus::prelude::IntoAttributeValue;
+                                    attr.into_value(self.bump)
+                                },
+                                ns,
+                                volatile,
+                            )
+                        );
+                        #builder_name {
+                            #(#forward_extended_fields,)*
+                            bump: self.bump,
+                            fields: ( #(#reconstructing,)* ),
+                            _phantom: self._phantom,
+                        }
+                    }
+                }
+
+                #(#extends_impl)*
+            })
+        }
+
         pub fn field_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {
             let StructInfo {
                 ref builder_name, ..
@@ -744,12 +933,11 @@ Finally, call `.build()` to create the instance of `{name}`.
                 ..
             } = field;
             // Add the bump lifetime to the generics
-            let mut ty_generics: Vec<syn::GenericArgument> = vec![syn::GenericArgument::Lifetime(
-                syn::Lifetime::new("'__bump", proc_macro2::Span::call_site()),
-            )];
-
-            ty_generics.extend(self.generics.params.iter().map(
-                |generic_param| match generic_param {
+            let mut ty_generics: Vec<syn::GenericArgument> = self
+                .generics
+                .params
+                .iter()
+                .map(|generic_param| match generic_param {
                     syn::GenericParam::Type(type_param) => {
                         let ident = type_param.ident.clone();
                         syn::parse(quote!(#ident).into()).unwrap()
@@ -761,19 +949,11 @@ Finally, call `.build()` to create the instance of `{name}`.
                         let ident = const_param.ident.clone();
                         syn::parse(quote!(#ident).into()).unwrap()
                     }
-                },
-            ));
+                })
+                .collect();
             let mut target_generics_tuple = empty_type_tuple();
             let mut ty_generics_tuple = empty_type_tuple();
             let generics = self.modify_generics(|g| {
-                // Add a bump lifetime to the generics
-                g.params.insert(
-                    0,
-                    syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new(
-                        "'__bump",
-                        proc_macro2::Span::call_site(),
-                    ))),
-                );
                 let index_after_lifetime_in_generics = g
                     .params
                     .iter()
@@ -851,6 +1031,11 @@ Finally, call `.build()` to create the instance of `{name}`.
             );
             let repeated_fields_error_message = format!("Repeated field {field_name}");
 
+            let forward_extended_fields = self.extend_fields().map(|f| {
+                let name = f.name;
+                quote!(#name: self.#name)
+            });
+
             Ok(quote! {
                 #[allow(dead_code, non_camel_case_types, missing_docs)]
                 impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause {
@@ -859,6 +1044,7 @@ Finally, call `.build()` to create the instance of `{name}`.
                         let #field_name = (#arg_expr,);
                         let ( #(#descructuring,)* ) = self.fields;
                         #builder_name {
+                            #(#forward_extended_fields,)*
                             bump: self.bump,
                             fields: ( #(#reconstructing,)* ),
                             _phantom: self._phantom,
@@ -893,13 +1079,11 @@ Finally, call `.build()` to create the instance of `{name}`.
                 ..
             } = field;
             // Add a bump lifetime to the generics
-            let mut builder_generics: Vec<syn::GenericArgument> =
-                vec![syn::GenericArgument::Lifetime(syn::Lifetime::new(
-                    "'__bump",
-                    proc_macro2::Span::call_site(),
-                ))];
-            builder_generics.extend(self.generics.params.iter().map(|generic_param| {
-                match generic_param {
+            let mut builder_generics: Vec<syn::GenericArgument> = self
+                .generics
+                .params
+                .iter()
+                .map(|generic_param| match generic_param {
                     syn::GenericParam::Type(type_param) => {
                         let ident = &type_param.ident;
                         syn::parse(quote!(#ident).into()).unwrap()
@@ -911,18 +1095,10 @@ Finally, call `.build()` to create the instance of `{name}`.
                         let ident = &const_param.ident;
                         syn::parse(quote!(#ident).into()).unwrap()
                     }
-                }
-            }));
+                })
+                .collect();
             let mut builder_generics_tuple = empty_type_tuple();
             let generics = self.modify_generics(|g| {
-                // Add a bump lifetime to the generics
-                g.params.insert(
-                    0,
-                    syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new(
-                        "'__bump",
-                        proc_macro2::Span::call_site(),
-                    ))),
-                );
                 let index_after_lifetime_in_generics = g
                     .params
                     .iter()
@@ -1007,15 +1183,6 @@ Finally, call `.build()` to create the instance of `{name}`.
             } = *self;
 
             let generics = self.modify_generics(|g| {
-                // Add a bump lifetime to the generics
-                g.params.insert(
-                    0,
-                    syn::GenericParam::Lifetime(syn::LifetimeParam::new(syn::Lifetime::new(
-                        "'__bump",
-                        proc_macro2::Span::call_site(),
-                    ))),
-                );
-
                 let index_after_lifetime_in_generics = g
                     .params
                     .iter()
@@ -1054,15 +1221,6 @@ Finally, call `.build()` to create the instance of `{name}`.
             let (_, ty_generics, where_clause) = self.generics.split_for_impl();
 
             let modified_ty_generics = modify_types_generics_hack(&ty_generics, |args| {
-                // Add a bump lifetime to the generics
-                args.insert(
-                    0,
-                    syn::GenericArgument::Lifetime(syn::Lifetime::new(
-                        "'__bump",
-                        proc_macro2::Span::call_site(),
-                    )),
-                );
-
                 args.insert(
                     0,
                     syn::GenericArgument::Type(
@@ -1088,7 +1246,9 @@ 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 let Some(ref default) = field.builder_attr.default {
+                if !field.builder_attr.extends.is_empty() {
+                    quote!(let #name = self.#name;)
+                } else if let Some(ref default) = field.builder_attr.default {
                     if field.builder_attr.skip {
                         quote!(let #name = #default;)
                     } else {

+ 4 - 3
packages/core/src/lib.rs

@@ -89,9 +89,10 @@ pub mod prelude {
         consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context,
         provide_context, provide_context_to_scope, provide_root_context, push_future,
         remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue,
-        AttributeType, Component, Element, Event, EventHandler, Fragment, IntoAttributeValue,
-        LazyNodes, MountedAttribute, Properties, Runtime, RuntimeGuard, Scope, ScopeId, ScopeState,
-        Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
+        AttributeType, Component, Element, Event, EventHandler, Fragment, HasAttributesBox,
+        IntoAttributeValue, LazyNodes, MountedAttribute, Properties, Runtime, RuntimeGuard, Scope,
+        ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw,
+        VNode, VirtualDom,
     };
 }
 

+ 2 - 2
packages/core/src/nodes.rs

@@ -899,12 +899,12 @@ impl<'a, T: IntoAttributeValue<'a>> IntoAttributeValue<'a> for Option<T> {
     }
 }
 
-pub trait HasAttributesBox<'a, T> {
+pub trait HasAttributesBox<'a> {
     fn push_attribute(
         self,
         name: &'a str,
         ns: Option<&'static str>,
         attr: impl IntoAttributeValue<'a>,
         volatile: bool,
-    ) -> T;
+    ) -> Self;
 }

+ 1 - 1
packages/html-internal-macro/src/lib.rs

@@ -78,7 +78,7 @@ impl ToTokens for ImplExtensionAttributes {
             pub trait #extension_name<'a> {
                 #(#defs)*
             }
-            impl<'a, T> #extension_name<'a> for T where T: HasAttributesBox<'a, T> + #marker_name {
+            impl<'a, T> #extension_name<'a> for T where T: HasAttributesBox<'a> + #marker_name {
                 #(#impls)*
             }
         });