From 3a683d43277520086430a24862eb88f59e19a690 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sun, 21 Jun 2026 10:08:23 +0200 Subject: [PATCH 1/6] Re-implement `remove_self_path` using visitor --- .../cgp-macro-core/src/types/blanket_trait.rs | 15 +++++ crates/macros/cgp-macro-core/src/types/mod.rs | 1 + .../macros/cgp-macro-core/src/visitors/mod.rs | 2 + .../src/visitors/remove_self_path.rs | 28 +++++++++ .../cgp-macro-lib/src/blanket_trait/derive.rs | 13 +---- .../cgp-macro-lib/src/blanket_trait/mod.rs | 2 - .../src/blanket_trait/remove_self_path.rs | 58 ------------------- 7 files changed, 49 insertions(+), 70 deletions(-) create mode 100644 crates/macros/cgp-macro-core/src/types/blanket_trait.rs create mode 100644 crates/macros/cgp-macro-core/src/visitors/remove_self_path.rs delete mode 100644 crates/macros/cgp-macro-lib/src/blanket_trait/remove_self_path.rs diff --git a/crates/macros/cgp-macro-core/src/types/blanket_trait.rs b/crates/macros/cgp-macro-core/src/types/blanket_trait.rs new file mode 100644 index 00000000..076e3902 --- /dev/null +++ b/crates/macros/cgp-macro-core/src/types/blanket_trait.rs @@ -0,0 +1,15 @@ +use syn::{Ident, ImplItem, ItemImpl, ItemTrait, TraitItem, TypeParamBound, WherePredicate}; + +use crate::parse_internal; + +pub struct ItemBlanketTrait { + pub context_ident: Ident, + pub item_trait: ItemTrait, +} + +impl ItemBlanketTrait { + pub fn to_item_impl(&self) -> syn::Result { + let Self { context_ident, item_trait } = self; + todo!() + } +} diff --git a/crates/macros/cgp-macro-core/src/types/mod.rs b/crates/macros/cgp-macro-core/src/types/mod.rs index 4c5a5b46..929a19df 100644 --- a/crates/macros/cgp-macro-core/src/types/mod.rs +++ b/crates/macros/cgp-macro-core/src/types/mod.rs @@ -17,3 +17,4 @@ pub mod keywords; pub mod namespace; pub mod path; pub mod provider_impl; +// pub mod blanket_trait; diff --git a/crates/macros/cgp-macro-core/src/visitors/mod.rs b/crates/macros/cgp-macro-core/src/visitors/mod.rs index 773cb309..04f460a7 100644 --- a/crates/macros/cgp-macro-core/src/visitors/mod.rs +++ b/crates/macros/cgp-macro-core/src/visitors/mod.rs @@ -1,8 +1,10 @@ +mod remove_self_path; mod replace_provider; mod replace_self; mod self_assoc_type; mod substitute_abstract_type; +pub use remove_self_path::*; pub use replace_provider::*; pub use replace_self::*; pub use self_assoc_type::*; diff --git a/crates/macros/cgp-macro-core/src/visitors/remove_self_path.rs b/crates/macros/cgp-macro-core/src/visitors/remove_self_path.rs new file mode 100644 index 00000000..60733da5 --- /dev/null +++ b/crates/macros/cgp-macro-core/src/visitors/remove_self_path.rs @@ -0,0 +1,28 @@ +use syn::visit_mut::{VisitMut, visit_path_mut, visit_type_mut}; +use syn::{Ident, PathArguments, Type, TypePath}; + +pub fn remove_self_path(path: &mut syn::Path, assoc_idents: &[Ident]) { + let mut visitor = RemoveSelfPathVisitor { assoc_idents }; + visit_path_mut(&mut visitor, path); +} + +struct RemoveSelfPathVisitor<'a> { + assoc_idents: &'a [Ident], +} + +impl VisitMut for RemoveSelfPathVisitor<'_> { + fn visit_type_mut(&mut self, node: &mut Type) { + if let Type::Path(TypePath { qself: None, path }) = node + && path.leading_colon.is_none() + && path.segments.len() >= 2 + && path.segments[0].ident == "Self" + && matches!(path.segments[0].arguments, PathArguments::None) + && self.assoc_idents.contains(&path.segments[1].ident) + { + let segments = std::mem::take(&mut path.segments); + path.segments = segments.into_iter().skip(1).collect(); + } + + visit_type_mut(self, node); + } +} diff --git a/crates/macros/cgp-macro-lib/src/blanket_trait/derive.rs b/crates/macros/cgp-macro-lib/src/blanket_trait/derive.rs index 0a377d89..10a6618b 100644 --- a/crates/macros/cgp-macro-lib/src/blanket_trait/derive.rs +++ b/crates/macros/cgp-macro-lib/src/blanket_trait/derive.rs @@ -1,3 +1,4 @@ +use cgp_macro_core::visitors::remove_self_path; use proc_macro2::Span; use quote::{ToTokens, quote}; use syn::token::{Eq, For, Impl, Semi}; @@ -6,8 +7,6 @@ use syn::{ TraitItem, Type, TypeParamBound, Visibility, WherePredicate, parse2, }; -use crate::blanket_trait::remove_self_path; - pub fn derive_blanket_trait( context_ident: &Ident, item_trait: &mut ItemTrait, @@ -40,10 +39,7 @@ pub fn derive_blanket_trait( for bound in current_assoc_bounds.iter_mut() { if let TypeParamBound::Trait(bound) = bound { - bound.path = parse2(remove_self_path( - bound.path.to_token_stream(), - &assoc_idents, - ))?; + remove_self_path(&mut bound.path, &assoc_idents); } } @@ -142,10 +138,7 @@ pub fn derive_blanket_trait( for bound in supertraits.iter_mut() { if let TypeParamBound::Trait(trait_bound) = bound { - trait_bound.path = parse2(remove_self_path( - trait_bound.path.to_token_stream(), - &assoc_idents, - ))?; + remove_self_path(&mut trait_bound.path, &assoc_idents); } } diff --git a/crates/macros/cgp-macro-lib/src/blanket_trait/mod.rs b/crates/macros/cgp-macro-lib/src/blanket_trait/mod.rs index 6663ad57..61eca8a7 100644 --- a/crates/macros/cgp-macro-lib/src/blanket_trait/mod.rs +++ b/crates/macros/cgp-macro-lib/src/blanket_trait/mod.rs @@ -1,5 +1,3 @@ mod derive; -mod remove_self_path; pub use derive::*; -pub use remove_self_path::*; diff --git a/crates/macros/cgp-macro-lib/src/blanket_trait/remove_self_path.rs b/crates/macros/cgp-macro-lib/src/blanket_trait/remove_self_path.rs deleted file mode 100644 index 62a75aee..00000000 --- a/crates/macros/cgp-macro-lib/src/blanket_trait/remove_self_path.rs +++ /dev/null @@ -1,58 +0,0 @@ -use itertools::Itertools; -use proc_macro2::{Group, TokenStream, TokenTree}; -use quote::format_ident; -use syn::Ident; - -pub fn remove_self_path(stream: TokenStream, assoc_idents: &Vec) -> TokenStream { - let self_type = format_ident!("Self"); - - let mut result_stream: Vec = Vec::new(); - - let mut token_iter = stream.into_iter().multipeek(); - - while let Some(tree) = token_iter.next() { - match tree { - TokenTree::Ident(ident) => { - if ident == self_type { - let m_colon_1 = token_iter.peek().cloned(); - let m_colon_2 = token_iter.peek().cloned(); - let assoc_ident = token_iter.peek().cloned(); - - match (m_colon_1, m_colon_2, assoc_ident) { - ( - Some(TokenTree::Punct(colon_1)), - Some(TokenTree::Punct(colon_2)), - Some(TokenTree::Ident(assoc_ident)), - ) if colon_1.as_char() == ':' - && colon_2.as_char() == ':' - && assoc_idents.contains(&assoc_ident) => - { - token_iter.next(); - token_iter.next(); - token_iter.next(); - - result_stream.push(TokenTree::Ident(assoc_ident)); - } - _ => { - result_stream.push(TokenTree::Ident(ident)); - } - } - } else { - result_stream.push(TokenTree::Ident(ident)); - } - } - TokenTree::Group(group) => { - let replaced_stream = remove_self_path(group.stream(), assoc_idents); - let replaced_group = Group::new(group.delimiter(), replaced_stream); - - result_stream.push(TokenTree::Group(replaced_group)); - } - TokenTree::Punct(punct) => { - result_stream.push(TokenTree::Punct(punct)); - } - TokenTree::Literal(lit) => result_stream.push(TokenTree::Literal(lit)), - } - } - - result_stream.into_iter().collect() -} From b77213eb9f1897996deeec184738069877795f82 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sun, 21 Jun 2026 10:13:57 +0200 Subject: [PATCH 2/6] Move blanket trait impl to cgp-macro-core --- .../cgp-macro-core/src/types/blanket_trait.rs | 178 +++++++++++++++++- crates/macros/cgp-macro-core/src/types/mod.rs | 2 +- .../cgp-macro-lib/src/blanket_trait/derive.rs | 170 ----------------- .../cgp-macro-lib/src/blanket_trait/mod.rs | 3 - .../src/entrypoints/blanket_trait.rs | 20 +- crates/macros/cgp-macro-lib/src/lib.rs | 1 - 6 files changed, 185 insertions(+), 189 deletions(-) delete mode 100644 crates/macros/cgp-macro-lib/src/blanket_trait/derive.rs delete mode 100644 crates/macros/cgp-macro-lib/src/blanket_trait/mod.rs diff --git a/crates/macros/cgp-macro-core/src/types/blanket_trait.rs b/crates/macros/cgp-macro-core/src/types/blanket_trait.rs index 076e3902..5e60afe7 100644 --- a/crates/macros/cgp-macro-core/src/types/blanket_trait.rs +++ b/crates/macros/cgp-macro-core/src/types/blanket_trait.rs @@ -1,6 +1,12 @@ -use syn::{Ident, ImplItem, ItemImpl, ItemTrait, TraitItem, TypeParamBound, WherePredicate}; +use proc_macro2::Span; +use quote::{ToTokens, quote}; +use syn::token::{Eq, Semi}; +use syn::{ + Error, Ident, ImplItem, ImplItemConst, ImplItemFn, ImplItemType, Item, ItemImpl, ItemTrait, + Path, TraitItem, Type, TypeParamBound, Visibility, WherePredicate, parse2, +}; -use crate::parse_internal; +use crate::visitors::remove_self_path; pub struct ItemBlanketTrait { pub context_ident: Ident, @@ -8,8 +14,172 @@ pub struct ItemBlanketTrait { } impl ItemBlanketTrait { + pub fn to_items(&self) -> syn::Result> { + let item_trait = self.item_trait.clone(); + let item_impl = self.to_item_impl()?; + + Ok(vec![item_trait.into(), item_impl.into()]) + } + pub fn to_item_impl(&self) -> syn::Result { - let Self { context_ident, item_trait } = self; - todo!() + let context_ident = &self.context_ident; + let mut item_trait = self.item_trait.clone(); + + let mut impl_items: Vec = Vec::new(); + + let mut assoc_idents: Vec = Vec::new(); + let mut assoc_bounds: Vec = Vec::new(); + + for trait_item in item_trait.items.iter() { + if let TraitItem::Type(trait_item_type) = trait_item { + let item_type_ident = &trait_item_type.ident; + assoc_idents.push(item_type_ident.clone()); + } + } + + for trait_item in item_trait.items.iter_mut() { + match trait_item { + TraitItem::Type(trait_item_type) => { + trait_item_type.default.take(); + + let item_type_ident = &trait_item_type.ident; + + let type_impl = parse2(quote! { + #item_type_ident + })?; + + if !trait_item_type.bounds.is_empty() { + let mut current_assoc_bounds = trait_item_type.bounds.clone(); + + for bound in current_assoc_bounds.iter_mut() { + if let TypeParamBound::Trait(bound) = bound { + remove_self_path(&mut bound.path, &assoc_idents); + } + } + + assoc_bounds.push(parse2(quote! { + #item_type_ident : #current_assoc_bounds + })?); + } + + let impl_item_type = ImplItemType { + attrs: trait_item_type.attrs.clone(), + vis: Visibility::Inherited, + defaultness: None, + type_token: trait_item_type.type_token, + ident: trait_item_type.ident.clone(), + generics: trait_item_type.generics.clone(), + eq_token: Eq(Span::call_site()), + ty: type_impl, + semi_token: Semi(Span::call_site()), + }; + + impl_items.push(ImplItem::Type(impl_item_type)); + } + TraitItem::Fn(trait_item_fn) => { + let fn_block = trait_item_fn + .default + .as_ref() + .ok_or_else(|| { + Error::new_spanned( + &trait_item_fn, + "function item require implementation block", + ) + })? + .clone(); + + trait_item_fn.default.take(); + + let impl_item_fn = ImplItemFn { + attrs: trait_item_fn.attrs.clone(), + vis: Visibility::Inherited, + defaultness: None, + sig: trait_item_fn.sig.clone(), + block: fn_block, + }; + + impl_items.push(ImplItem::Fn(impl_item_fn)); + } + TraitItem::Const(trait_item_const) => { + let (eq_token, const_expr) = trait_item_const + .default + .as_ref() + .ok_or_else(|| { + Error::new_spanned( + &trait_item_const, + "const item require implementation expression", + ) + })? + .clone(); + + trait_item_const.default.take(); + + let impl_item_const = ImplItemConst { + attrs: trait_item_const.attrs.clone(), + vis: Visibility::Inherited, + defaultness: None, + const_token: trait_item_const.const_token, + ident: trait_item_const.ident.clone(), + generics: trait_item_const.generics.clone(), + colon_token: trait_item_const.colon_token, + ty: trait_item_const.ty.clone(), + eq_token, + expr: const_expr, + semi_token: trait_item_const.semi_token, + }; + + impl_items.push(ImplItem::Const(impl_item_const)); + } + _ => return Err(Error::new_spanned(&trait_item, "unsupported trait item")), + } + } + + let context_type: Type = parse2(quote! { #context_ident })?; + + let mut impl_generics = item_trait.generics.clone(); + + impl_generics + .params + .push(parse2(context_type.to_token_stream())?); + + for assoc_ident in assoc_idents.iter() { + impl_generics + .params + .push(parse2(assoc_ident.to_token_stream())?); + } + + let mut supertraits = item_trait.supertraits.clone(); + + for bound in supertraits.iter_mut() { + if let TypeParamBound::Trait(trait_bound) = bound { + remove_self_path(&mut trait_bound.path, &assoc_idents); + } + } + + let where_clause = impl_generics.make_where_clause(); + where_clause.predicates.push(parse2(quote! { + #context_type: #supertraits + })?); + + where_clause.predicates.extend(assoc_bounds); + + let trait_name = &item_trait.ident; + let (_, type_generics, _) = item_trait.generics.split_for_impl(); + + let trait_path: Path = parse2(quote! { #trait_name #type_generics })?; + + let item_impl = ItemImpl { + attrs: item_trait.attrs.clone(), + defaultness: None, + unsafety: item_trait.unsafety, + impl_token: Default::default(), + generics: impl_generics, + trait_: Some((None, trait_path, Default::default())), + self_ty: Box::new(context_type), + brace_token: item_trait.brace_token, + items: impl_items, + }; + + Ok(item_impl) } } diff --git a/crates/macros/cgp-macro-core/src/types/mod.rs b/crates/macros/cgp-macro-core/src/types/mod.rs index 929a19df..e54d1834 100644 --- a/crates/macros/cgp-macro-core/src/types/mod.rs +++ b/crates/macros/cgp-macro-core/src/types/mod.rs @@ -1,4 +1,5 @@ pub mod attributes; +pub mod blanket_trait; pub mod cgp_auto_getter; pub mod cgp_component; pub mod cgp_fn; @@ -17,4 +18,3 @@ pub mod keywords; pub mod namespace; pub mod path; pub mod provider_impl; -// pub mod blanket_trait; diff --git a/crates/macros/cgp-macro-lib/src/blanket_trait/derive.rs b/crates/macros/cgp-macro-lib/src/blanket_trait/derive.rs deleted file mode 100644 index 10a6618b..00000000 --- a/crates/macros/cgp-macro-lib/src/blanket_trait/derive.rs +++ /dev/null @@ -1,170 +0,0 @@ -use cgp_macro_core::visitors::remove_self_path; -use proc_macro2::Span; -use quote::{ToTokens, quote}; -use syn::token::{Eq, For, Impl, Semi}; -use syn::{ - Error, Ident, ImplItem, ImplItemConst, ImplItemFn, ImplItemType, ItemImpl, ItemTrait, Path, - TraitItem, Type, TypeParamBound, Visibility, WherePredicate, parse2, -}; - -pub fn derive_blanket_trait( - context_ident: &Ident, - item_trait: &mut ItemTrait, -) -> syn::Result { - let mut impl_items: Vec = Vec::new(); - - let mut assoc_idents: Vec = Vec::new(); - let mut assoc_bounds: Vec = Vec::new(); - - for trait_item in item_trait.items.iter() { - if let TraitItem::Type(trait_item_type) = trait_item { - let item_type_ident = &trait_item_type.ident; - assoc_idents.push(item_type_ident.clone()); - } - } - - for trait_item in item_trait.items.iter_mut() { - match trait_item { - TraitItem::Type(trait_item_type) => { - trait_item_type.default.take(); - - let item_type_ident = &trait_item_type.ident; - - let type_impl = parse2(quote! { - #item_type_ident - })?; - - if !trait_item_type.bounds.is_empty() { - let mut current_assoc_bounds = trait_item_type.bounds.clone(); - - for bound in current_assoc_bounds.iter_mut() { - if let TypeParamBound::Trait(bound) = bound { - remove_self_path(&mut bound.path, &assoc_idents); - } - } - - assoc_bounds.push(parse2(quote! { - #item_type_ident : #current_assoc_bounds - })?); - } - - let impl_item_type = ImplItemType { - attrs: trait_item_type.attrs.clone(), - vis: Visibility::Inherited, - defaultness: None, - type_token: trait_item_type.type_token, - ident: trait_item_type.ident.clone(), - generics: trait_item_type.generics.clone(), - eq_token: Eq(Span::call_site()), - ty: type_impl, - semi_token: Semi(Span::call_site()), - }; - - impl_items.push(ImplItem::Type(impl_item_type)); - } - TraitItem::Fn(trait_item_fn) => { - let fn_block = trait_item_fn - .default - .as_ref() - .ok_or_else(|| { - Error::new_spanned( - &trait_item_fn, - "function item require implementation block", - ) - })? - .clone(); - - trait_item_fn.default.take(); - - let impl_item_fn = ImplItemFn { - attrs: trait_item_fn.attrs.clone(), - vis: Visibility::Inherited, - defaultness: None, - sig: trait_item_fn.sig.clone(), - block: fn_block, - }; - - impl_items.push(ImplItem::Fn(impl_item_fn)); - } - TraitItem::Const(trait_item_const) => { - let (eq_token, const_expr) = trait_item_const - .default - .as_ref() - .ok_or_else(|| { - Error::new_spanned( - &trait_item_const, - "const item require implementation expression", - ) - })? - .clone(); - - trait_item_const.default.take(); - - let impl_item_const = ImplItemConst { - attrs: trait_item_const.attrs.clone(), - vis: Visibility::Inherited, - defaultness: None, - const_token: trait_item_const.const_token, - ident: trait_item_const.ident.clone(), - generics: trait_item_const.generics.clone(), - colon_token: trait_item_const.colon_token, - ty: trait_item_const.ty.clone(), - eq_token, - expr: const_expr, - semi_token: trait_item_const.semi_token, - }; - - impl_items.push(ImplItem::Const(impl_item_const)); - } - _ => return Err(Error::new_spanned(&trait_item, "unsupported trait item")), - } - } - - let context_type: Type = parse2(quote! { #context_ident })?; - - let mut impl_generics = item_trait.generics.clone(); - - impl_generics - .params - .push(parse2(context_type.to_token_stream())?); - - for assoc_ident in assoc_idents.iter() { - impl_generics - .params - .push(parse2(assoc_ident.to_token_stream())?); - } - - let mut supertraits = item_trait.supertraits.clone(); - - for bound in supertraits.iter_mut() { - if let TypeParamBound::Trait(trait_bound) = bound { - remove_self_path(&mut trait_bound.path, &assoc_idents); - } - } - - let where_clause = impl_generics.make_where_clause(); - where_clause.predicates.push(parse2(quote! { - #context_type: #supertraits - })?); - - where_clause.predicates.extend(assoc_bounds); - - let trait_name = &item_trait.ident; - let (_, type_generics, _) = item_trait.generics.split_for_impl(); - - let trait_path: Path = parse2(quote! { #trait_name #type_generics })?; - - let item_impl = ItemImpl { - attrs: item_trait.attrs.clone(), - defaultness: None, - unsafety: item_trait.unsafety, - impl_token: Impl(Span::call_site()), - generics: impl_generics, - trait_: Some((None, trait_path, For(Span::call_site()))), - self_ty: Box::new(context_type), - brace_token: item_trait.brace_token, - items: impl_items, - }; - - Ok(item_impl) -} diff --git a/crates/macros/cgp-macro-lib/src/blanket_trait/mod.rs b/crates/macros/cgp-macro-lib/src/blanket_trait/mod.rs deleted file mode 100644 index 61eca8a7..00000000 --- a/crates/macros/cgp-macro-lib/src/blanket_trait/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod derive; - -pub use derive::*; diff --git a/crates/macros/cgp-macro-lib/src/entrypoints/blanket_trait.rs b/crates/macros/cgp-macro-lib/src/entrypoints/blanket_trait.rs index 19623875..fd1ce2d5 100644 --- a/crates/macros/cgp-macro-lib/src/entrypoints/blanket_trait.rs +++ b/crates/macros/cgp-macro-lib/src/entrypoints/blanket_trait.rs @@ -1,9 +1,8 @@ +use cgp_macro_core::types::blanket_trait::ItemBlanketTrait; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Ident, ItemTrait, parse2}; -use crate::blanket_trait::derive_blanket_trait; - pub fn blanket_trait(attr: TokenStream, body: TokenStream) -> syn::Result { let context_ident: Ident = if attr.is_empty() { Ident::new("__Context__", Span::call_site()) @@ -11,15 +10,16 @@ pub fn blanket_trait(attr: TokenStream, body: TokenStream) -> syn::Result Date: Sun, 21 Jun 2026 10:25:43 +0200 Subject: [PATCH 3/6] Remove self path on the whole item trait directly --- .../cgp-macro-core/src/types/blanket_trait.rs | 26 +++++++------------ .../src/visitors/remove_self_path.rs | 11 +++----- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/crates/macros/cgp-macro-core/src/types/blanket_trait.rs b/crates/macros/cgp-macro-core/src/types/blanket_trait.rs index 5e60afe7..5895f93f 100644 --- a/crates/macros/cgp-macro-core/src/types/blanket_trait.rs +++ b/crates/macros/cgp-macro-core/src/types/blanket_trait.rs @@ -1,12 +1,13 @@ use proc_macro2::Span; use quote::{ToTokens, quote}; use syn::token::{Eq, Semi}; +use syn::visit_mut::VisitMut; use syn::{ Error, Ident, ImplItem, ImplItemConst, ImplItemFn, ImplItemType, Item, ItemImpl, ItemTrait, - Path, TraitItem, Type, TypeParamBound, Visibility, WherePredicate, parse2, + Path, TraitItem, Type, Visibility, WherePredicate, parse2, }; -use crate::visitors::remove_self_path; +use crate::visitors::RemoveSelfPathVisitor; pub struct ItemBlanketTrait { pub context_ident: Ident, @@ -37,6 +38,11 @@ impl ItemBlanketTrait { } } + RemoveSelfPathVisitor { + assoc_idents: &assoc_idents, + } + .visit_item_trait_mut(&mut item_trait); + for trait_item in item_trait.items.iter_mut() { match trait_item { TraitItem::Type(trait_item_type) => { @@ -49,13 +55,7 @@ impl ItemBlanketTrait { })?; if !trait_item_type.bounds.is_empty() { - let mut current_assoc_bounds = trait_item_type.bounds.clone(); - - for bound in current_assoc_bounds.iter_mut() { - if let TypeParamBound::Trait(bound) = bound { - remove_self_path(&mut bound.path, &assoc_idents); - } - } + let current_assoc_bounds = trait_item_type.bounds.clone(); assoc_bounds.push(parse2(quote! { #item_type_ident : #current_assoc_bounds @@ -148,13 +148,7 @@ impl ItemBlanketTrait { .push(parse2(assoc_ident.to_token_stream())?); } - let mut supertraits = item_trait.supertraits.clone(); - - for bound in supertraits.iter_mut() { - if let TypeParamBound::Trait(trait_bound) = bound { - remove_self_path(&mut trait_bound.path, &assoc_idents); - } - } + let supertraits = item_trait.supertraits.clone(); let where_clause = impl_generics.make_where_clause(); where_clause.predicates.push(parse2(quote! { diff --git a/crates/macros/cgp-macro-core/src/visitors/remove_self_path.rs b/crates/macros/cgp-macro-core/src/visitors/remove_self_path.rs index 60733da5..646f501a 100644 --- a/crates/macros/cgp-macro-core/src/visitors/remove_self_path.rs +++ b/crates/macros/cgp-macro-core/src/visitors/remove_self_path.rs @@ -1,13 +1,8 @@ -use syn::visit_mut::{VisitMut, visit_path_mut, visit_type_mut}; +use syn::visit_mut::{VisitMut, visit_type_mut}; use syn::{Ident, PathArguments, Type, TypePath}; -pub fn remove_self_path(path: &mut syn::Path, assoc_idents: &[Ident]) { - let mut visitor = RemoveSelfPathVisitor { assoc_idents }; - visit_path_mut(&mut visitor, path); -} - -struct RemoveSelfPathVisitor<'a> { - assoc_idents: &'a [Ident], +pub struct RemoveSelfPathVisitor<'a> { + pub assoc_idents: &'a [Ident], } impl VisitMut for RemoveSelfPathVisitor<'_> { From e63d6b24e6a4912d7d6dad587d9b4489d72be380 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sun, 21 Jun 2026 10:28:11 +0200 Subject: [PATCH 4/6] Use parse_internal --- .../cgp-macro-core/src/types/blanket_trait.rs | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/crates/macros/cgp-macro-core/src/types/blanket_trait.rs b/crates/macros/cgp-macro-core/src/types/blanket_trait.rs index 5895f93f..13f87263 100644 --- a/crates/macros/cgp-macro-core/src/types/blanket_trait.rs +++ b/crates/macros/cgp-macro-core/src/types/blanket_trait.rs @@ -1,12 +1,12 @@ use proc_macro2::Span; -use quote::{ToTokens, quote}; use syn::token::{Eq, Semi}; use syn::visit_mut::VisitMut; use syn::{ Error, Ident, ImplItem, ImplItemConst, ImplItemFn, ImplItemType, Item, ItemImpl, ItemTrait, - Path, TraitItem, Type, Visibility, WherePredicate, parse2, + Path, TraitItem, Type, Visibility, WherePredicate, }; +use crate::parse_internal; use crate::visitors::RemoveSelfPathVisitor; pub struct ItemBlanketTrait { @@ -50,16 +50,16 @@ impl ItemBlanketTrait { let item_type_ident = &trait_item_type.ident; - let type_impl = parse2(quote! { + let type_impl = parse_internal! { #item_type_ident - })?; + }; if !trait_item_type.bounds.is_empty() { let current_assoc_bounds = trait_item_type.bounds.clone(); - assoc_bounds.push(parse2(quote! { + assoc_bounds.push(parse_internal! { #item_type_ident : #current_assoc_bounds - })?); + }); } let impl_item_type = ImplItemType { @@ -134,33 +134,31 @@ impl ItemBlanketTrait { } } - let context_type: Type = parse2(quote! { #context_ident })?; + let context_type: Type = parse_internal!(#context_ident); let mut impl_generics = item_trait.generics.clone(); - impl_generics - .params - .push(parse2(context_type.to_token_stream())?); + impl_generics.params.push(parse_internal!(#context_type)); for assoc_ident in assoc_idents.iter() { - impl_generics - .params - .push(parse2(assoc_ident.to_token_stream())?); + impl_generics.params.push(parse_internal!(#assoc_ident)); } let supertraits = item_trait.supertraits.clone(); let where_clause = impl_generics.make_where_clause(); - where_clause.predicates.push(parse2(quote! { + where_clause.predicates.push(parse_internal! { #context_type: #supertraits - })?); + }); where_clause.predicates.extend(assoc_bounds); let trait_name = &item_trait.ident; let (_, type_generics, _) = item_trait.generics.split_for_impl(); - let trait_path: Path = parse2(quote! { #trait_name #type_generics })?; + let trait_path: Path = parse_internal! { + #trait_name #type_generics + }; let item_impl = ItemImpl { attrs: item_trait.attrs.clone(), From bfd75a793fdd3dc6b56433454ecd0e5beec3b354 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sun, 21 Jun 2026 10:29:09 +0200 Subject: [PATCH 5/6] Slight refactoring --- crates/macros/cgp-macro-core/src/types/blanket_trait.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/macros/cgp-macro-core/src/types/blanket_trait.rs b/crates/macros/cgp-macro-core/src/types/blanket_trait.rs index 13f87263..8cab3dc2 100644 --- a/crates/macros/cgp-macro-core/src/types/blanket_trait.rs +++ b/crates/macros/cgp-macro-core/src/types/blanket_trait.rs @@ -1,5 +1,3 @@ -use proc_macro2::Span; -use syn::token::{Eq, Semi}; use syn::visit_mut::VisitMut; use syn::{ Error, Ident, ImplItem, ImplItemConst, ImplItemFn, ImplItemType, Item, ItemImpl, ItemTrait, @@ -69,9 +67,9 @@ impl ItemBlanketTrait { type_token: trait_item_type.type_token, ident: trait_item_type.ident.clone(), generics: trait_item_type.generics.clone(), - eq_token: Eq(Span::call_site()), + eq_token: Default::default(), ty: type_impl, - semi_token: Semi(Span::call_site()), + semi_token: Default::default(), }; impl_items.push(ImplItem::Type(impl_item_type)); From 9f8d6c486b13e2ec0c420134d1de483ea42b2730 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sun, 21 Jun 2026 10:39:33 +0200 Subject: [PATCH 6/6] Use snapshot testing for blanket_trait --- .../src/entrypoints/mod.rs | 2 + .../src/entrypoints/snapshot_blanket_trait.rs | 14 +++ .../cgp-macro-test-util-lib/src/keywords.rs | 2 + crates/macros/cgp-macro-test-util/src/lib.rs | 7 ++ .../cgp-tests/src/tests/blanket_trait.rs | 113 ++++++++++++++---- 5 files changed, 116 insertions(+), 22 deletions(-) create mode 100644 crates/macros/cgp-macro-test-util-lib/src/entrypoints/snapshot_blanket_trait.rs diff --git a/crates/macros/cgp-macro-test-util-lib/src/entrypoints/mod.rs b/crates/macros/cgp-macro-test-util-lib/src/entrypoints/mod.rs index cca0dee2..3ca8cda5 100644 --- a/crates/macros/cgp-macro-test-util-lib/src/entrypoints/mod.rs +++ b/crates/macros/cgp-macro-test-util-lib/src/entrypoints/mod.rs @@ -1,3 +1,4 @@ +mod snapshot_blanket_trait; mod snapshot_cgp_auto_getter; mod snapshot_cgp_component; mod snapshot_cgp_fn; @@ -11,6 +12,7 @@ mod snapshot_check_components; mod snapshot_delegate_and_check_components; mod snapshot_delegate_components; +pub use snapshot_blanket_trait::*; pub use snapshot_cgp_auto_getter::*; pub use snapshot_cgp_component::*; pub use snapshot_cgp_fn::*; diff --git a/crates/macros/cgp-macro-test-util-lib/src/entrypoints/snapshot_blanket_trait.rs b/crates/macros/cgp-macro-test-util-lib/src/entrypoints/snapshot_blanket_trait.rs new file mode 100644 index 00000000..63121f00 --- /dev/null +++ b/crates/macros/cgp-macro-test-util-lib/src/entrypoints/snapshot_blanket_trait.rs @@ -0,0 +1,14 @@ +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{ItemTrait, parse2}; + +use crate::keywords::BlanketTrait; +use crate::types::AttributeMacroSnapshot; + +pub fn snapshot_blanket_trait(body: TokenStream) -> syn::Result { + let item: AttributeMacroSnapshot = parse2(body)?; + + let output = cgp_macro_lib::blanket_trait(item.attr, item.body.to_token_stream())?; + + item.snapshot.wrap_output(output) +} diff --git a/crates/macros/cgp-macro-test-util-lib/src/keywords.rs b/crates/macros/cgp-macro-test-util-lib/src/keywords.rs index 91c69aaf..54e1b3ef 100644 --- a/crates/macros/cgp-macro-test-util-lib/src/keywords.rs +++ b/crates/macros/cgp-macro-test-util-lib/src/keywords.rs @@ -23,3 +23,5 @@ define_keyword!(DelegateComponents, "delegate_components"); define_keyword!(CheckComponents, "check_components"); define_keyword!(DelegateAndCheckComponents, "delegate_and_check_components"); + +define_keyword!(BlanketTrait, "blanket_trait"); diff --git a/crates/macros/cgp-macro-test-util/src/lib.rs b/crates/macros/cgp-macro-test-util/src/lib.rs index 0b32b625..1fa6ca61 100644 --- a/crates/macros/cgp-macro-test-util/src/lib.rs +++ b/crates/macros/cgp-macro-test-util/src/lib.rs @@ -84,3 +84,10 @@ pub fn snapshot_delegate_and_check_components(body: TokenStream) -> TokenStream .unwrap_or_else(syn::Error::into_compile_error) .into() } + +#[proc_macro] +pub fn snapshot_blanket_trait(body: TokenStream) -> TokenStream { + entrypoints::snapshot_blanket_trait(body.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} diff --git a/crates/tests/cgp-tests/src/tests/blanket_trait.rs b/crates/tests/cgp-tests/src/tests/blanket_trait.rs index 3e8a415b..d23e8caa 100644 --- a/crates/tests/cgp-tests/src/tests/blanket_trait.rs +++ b/crates/tests/cgp-tests/src/tests/blanket_trait.rs @@ -1,14 +1,25 @@ #![allow(dead_code)] -use cgp::core::macros::blanket_trait; +mod basic_blanket_trait { + use cgp_macro_test_util::snapshot_blanket_trait; -#[test] -pub fn test_basic_blanket_trait() { pub trait Foo {} pub trait Bar {} - #[blanket_trait] - pub trait FooBar: Foo + Bar {} + snapshot_blanket_trait! { + #[blanket_trait] + pub trait FooBar: Foo + Bar {} + + expand_foo_bar(output) { + insta::assert_snapshot!(output, @" + pub trait FooBar: Foo + Bar {} + impl<__Context__> FooBar for __Context__ + where + __Context__: Foo + Bar, + {} + ") + } + } pub struct Context; @@ -19,8 +30,9 @@ pub fn test_basic_blanket_trait() { impl CanUseFooBar for Context {} } -#[test] -pub fn test_blanket_trait_with_method() { +mod blanket_trait_with_method { + use cgp_macro_test_util::snapshot_blanket_trait; + pub trait Foo { fn foo(&self); } @@ -28,11 +40,33 @@ pub fn test_blanket_trait_with_method() { fn bar(&self); } - #[blanket_trait] - pub trait FooBar: Foo + Bar { - fn foo_bar(&self) { - self.foo(); - self.bar(); + snapshot_blanket_trait! { + #[blanket_trait] + pub trait FooBar: Foo + Bar { + fn foo_bar(&self) { + self.foo(); + self.bar(); + } + } + + expand_foo_bar(output) { + insta::assert_snapshot!(output, @" + pub trait FooBar: Foo + Bar { + fn foo_bar(&self) { + self.foo(); + self.bar(); + } + } + impl<__Context__> FooBar for __Context__ + where + __Context__: Foo + Bar, + { + fn foo_bar(&self) { + self.foo(); + self.bar(); + } + } + ") } } @@ -50,17 +84,34 @@ pub fn test_blanket_trait_with_method() { impl CanUseFooBar for Context {} } -#[test] -pub fn test_blanket_trait_with_associated_type_without_constraints() { +mod blanket_trait_with_associated_type_without_constraints { + use cgp_macro_test_util::snapshot_blanket_trait; + pub trait HasFooTypeAt { type Foo; } pub struct Bar; - #[blanket_trait] - pub trait HasFooTypeAtBar: HasFooTypeAt { - type FooBar; + snapshot_blanket_trait! { + #[blanket_trait] + pub trait HasFooTypeAtBar: HasFooTypeAt { + type FooBar; + } + + expand_foo_bar(output) { + insta::assert_snapshot!(output, @" + pub trait HasFooTypeAtBar: HasFooTypeAt { + type FooBar; + } + impl<__Context__, FooBar> HasFooTypeAtBar for __Context__ + where + __Context__: HasFooTypeAt, + { + type FooBar = FooBar; + } + ") + } } pub struct Context; @@ -74,17 +125,35 @@ pub fn test_blanket_trait_with_associated_type_without_constraints() { impl CanUseFooTypeAtBar for Context {} } -#[test] -pub fn test_blanket_trait_with_associated_type_and_constraints() { +mod blanket_trait_with_associated_type_and_constraints { + use cgp_macro_test_util::snapshot_blanket_trait; + pub trait HasFooTypeAt { type Foo; } pub struct Bar; - #[blanket_trait] - pub trait HasFooTypeAtBar: HasFooTypeAt { - type FooBar: Clone; + snapshot_blanket_trait! { + #[blanket_trait] + pub trait HasFooTypeAtBar: HasFooTypeAt { + type FooBar: Clone; + } + + expand_foo_bar(output) { + insta::assert_snapshot!(output, @" + pub trait HasFooTypeAtBar: HasFooTypeAt { + type FooBar: Clone; + } + impl<__Context__, FooBar> HasFooTypeAtBar for __Context__ + where + __Context__: HasFooTypeAt, + FooBar: Clone, + { + type FooBar = FooBar; + } + ") + } } pub struct Context;