macro_tools/
generic_params.rs

1//!
2//! Functions and structures to handle and manipulate generic parameters using the `syn` crate. It's designed to support macro-driven code generation by simplifying, merging, extracting, and decomposing `syn::Generics`.
3//!
4
5/// Define a private namespace for all its items.
6mod private
7{
8  #[ allow( clippy::wildcard_imports ) ]
9  use crate::*;
10  use crate::IterTrait;
11  // use iter_tools::IterTrait;
12
13  /// A `GenericsWithWhere` struct to handle the parsing of Rust generics with an explicit `where` clause.
14  ///
15  /// This wrapper addresses the limitation in the `syn` crate where parsing `Generics` directly from a `ParseStream`
16  /// does not automatically handle associated `where` clauses. By integrating `where` clause parsing into the
17  /// `GenericsWithWhere`, this struct provides a seamless way to capture both the generics and their constraints
18  /// in scenarios where the `where` clause is crucial for type constraints and bounds in Rust macros and code generation.
19  ///
20  /// Usage:
21  ///
22  /// ```
23  /// let parsed_generics : macro_tools::GenericsWithWhere
24  /// = syn::parse_str( "< T : Clone, U : Default = Default1 > where T : Default" ).unwrap();
25  /// assert!( parsed_generics.generics.params.len() == 2 );
26  /// assert!( parsed_generics.generics.where_clause.is_some() );
27  /// ```
28  ///
29
30  #[ derive( Debug ) ]
31  pub struct GenericsWithWhere
32  {
33    /// Syn's generics parameters.
34    pub generics : syn::Generics,
35  }
36
37  impl GenericsWithWhere
38  {
39    /// Unwraps the `GenericsWithWhere` to retrieve the inner `syn::Generics`.
40    #[ must_use ]
41    pub fn unwrap( self ) -> syn::Generics
42    {
43      self.generics
44    }
45
46    /// Parses a string to a `GenericsWithWhere`, specifically designed to handle generics syntax with where clauses effectively.
47    /// # Errors
48    /// qqq: doc
49    pub fn parse_from_str( s : &str ) -> syn::Result< GenericsWithWhere >
50    {
51      syn::parse_str::< GenericsWithWhere >( s )
52    }
53  }
54
55  impl syn::parse::Parse for GenericsWithWhere
56  {
57    fn parse( input : syn::parse::ParseStream< '_ > ) -> syn::Result< Self >
58    {
59      let generics : syn::Generics = input.parse()?;
60      let where_clause : Option< syn::WhereClause > = input.parse()?;
61
62      let mut generics_clone = generics.clone();
63      generics_clone.where_clause = where_clause;
64
65      Ok( GenericsWithWhere
66      {
67        generics : generics_clone,
68      })
69    }
70  }
71
72  impl quote::ToTokens for GenericsWithWhere
73  {
74    fn to_tokens( &self, tokens : &mut proc_macro2::TokenStream )
75    {
76      self.generics.to_tokens( tokens );
77    }
78  }
79
80  impl From<GenericsWithWhere> for syn::Generics
81  {
82    fn from( g : GenericsWithWhere ) -> Self
83    {
84      g.generics
85    }
86  }
87
88  impl From<syn::Generics> for GenericsWithWhere
89  {
90    fn from( generics : syn::Generics ) -> Self
91    {
92      GenericsWithWhere { generics }
93    }
94  }
95
96  /// Merges two `syn::Generics` instances into a new one.
97  ///
98  /// This function takes two references to `syn::Generics` and combines their
99  /// type parameters and where clauses into a new `syn::Generics` instance. If
100  /// both instances have where clauses, the predicates of these clauses are merged
101  /// into a single where clause.
102  ///
103  /// # Arguments
104  ///
105  /// * `a` - A reference to the first `syn::Generics` instance.
106  /// * `b` - A reference to the second `syn::Generics` instance.
107  ///
108  /// # Returns
109  ///
110  /// Returns a new `syn::Generics` instance containing the merged type parameters
111  /// and where clauses from `a` and `b`.
112  ///
113  /// # Examples
114  ///
115  ///
116  /// # use `syn::{Generics`, `parse_quote`};
117  ///
118  /// let mut `generics_a` : `syn::Generics` = `parse_quote`!{ < T : Clone, U : Default > };
119  /// `generics_a.where_clause` = `parse_quote`!{ where T : Default };
120  /// let mut `generics_b` : `syn::Generics` = `parse_quote`!{ < V : `core::fmt::Debug` > };
121  /// `generics_b.where_clause` = `parse_quote`!{ where V : Sized };
122  /// let got = `generic_params::merge`( &`generics_a`, &`generics_b` );
123  ///
124  /// let mut exp : `syn::Generics` = `parse_quote`!
125  /// {
126  ///   < T : Clone, U : Default, V : `core::fmt::Debug` >
127  /// };
128  /// `exp.where_clause` = `parse_quote`!
129  /// {
130  ///   where
131  ///     T : Default,
132  ///     V : Sized
133  /// };
134  ///
135  /// `assert_eq`!( got, exp );
136  #[ must_use ]
137  #[ allow( clippy::default_trait_access ) ]
138  pub fn merge( a : &syn::Generics, b : &syn::Generics ) -> syn::Generics
139  {
140
141    let mut result = syn::Generics
142    {
143      params : Default::default(),
144      where_clause : None,
145      lt_token : Some( syn::token::Lt::default() ),
146      gt_token : Some( syn::token::Gt::default() ),
147    };
148
149    // Merge params
150    // result.params.extend( a.params.iter().chain( b.params.iter() ) );
151    for param in &a.params
152    {
153      result.params.push( param.clone() );
154    }
155    for param in &b.params
156    {
157      result.params.push( param.clone() );
158    }
159
160    // Merge where clauses
161    result.where_clause = match( &a.where_clause, &b.where_clause )
162    {
163      ( Some( a_clause ), Some( b_clause ) ) =>
164      {
165        let mut merged_where_clause = syn::WhereClause
166        {
167          where_token: a_clause.where_token,
168          predicates: a_clause.predicates.clone(),
169        };
170        for predicate in &b_clause.predicates
171        {
172          merged_where_clause.predicates.push( predicate.clone() );
173        }
174        Some( merged_where_clause )
175      },
176      ( Some( a_clause ), None ) => Some( a_clause.clone() ),
177      ( None, Some( b_clause ) ) => Some( b_clause.clone() ),
178      _ => None,
179    };
180
181    result
182  }
183
184  /// Extracts parameter names from the given `Generics`,
185  /// dropping bounds, defaults, and the where clause.
186  ///
187  /// This function simplifies the generics to include only the names of the type parameters,
188  /// lifetimes, and const parameters, without any of their associated bounds or default values.
189  /// The resulting `Generics` will have an empty where clause.
190  ///
191  /// # Arguments
192  ///
193  /// * `generics` - The `Generics` instance from which to extract parameter names.
194  ///
195  /// # Returns
196  ///
197  /// Returns a new `Generics` instance containing only the names of the parameters.
198  ///
199  /// # Examples
200  ///
201  /// ```rust
202  /// # use macro_tools::syn::parse_quote;
203  ///
204  /// let mut generics : syn::Generics = parse_quote!{ < T : Clone + Default, U, 'a, const N : usize > };
205  /// generics.where_clause = parse_quote!{ where T: core::fmt::Debug };
206  /// // let generics : Generics = parse_quote!{ < T : Clone + Default, U, 'a, const N : usize > where T: core::fmt::Debug };
207  /// let simplified_generics = macro_tools::generic_params::only_names( &generics );
208  ///
209  /// assert_eq!( simplified_generics.params.len(), 4 ); // Contains T, U, 'a, and N
210  /// assert!( simplified_generics.where_clause.is_none() ); // Where clause is removed
211  /// ```
212  #[ allow( clippy::default_trait_access ) ]
213  #[ must_use ]
214  pub fn only_names( generics : &syn::Generics ) -> syn::Generics
215  {
216    // use syn::{ Generics, GenericParam, LifetimeDef, TypeParam, ConstParam };
217    use syn::{ Generics, GenericParam, LifetimeParam, TypeParam, ConstParam };
218
219    let result = Generics
220    {
221      params : generics.params.iter().map( | param | match param
222      {
223        GenericParam::Type( TypeParam { ident, .. } ) => GenericParam::Type( TypeParam
224        {
225          attrs : Vec::new(),
226          ident : ident.clone(),
227          colon_token : None,
228          bounds : Default::default(),
229          eq_token : None,
230          default : None,
231        }),
232        GenericParam::Lifetime( LifetimeParam { lifetime, .. } ) => GenericParam::Lifetime( LifetimeParam
233        {
234          attrs : Vec::new(),
235          lifetime : lifetime.clone(),
236          colon_token : None,
237          bounds : Default::default(),
238        }),
239        GenericParam::Const( ConstParam { ident, ty, .. } ) => GenericParam::Const( ConstParam
240        {
241          attrs : Vec::new(),
242          const_token : Default::default(),
243          ident : ident.clone(),
244          colon_token : Default::default(),
245          ty : ty.clone(),
246          eq_token : Default::default(),
247          default : None,
248        }),
249      }).collect(),
250      where_clause : None,
251      lt_token : generics.lt_token,
252      gt_token : generics.gt_token,
253    };
254
255    result
256  }
257
258  /// Extracts the names of type parameters, lifetimes, and const parameters from the given `Generics`.
259  ///
260  /// This function returns an iterator over the names of the parameters in the `Generics`,
261  /// which can be useful for generating code that requires just the names of the parameters
262  /// without their associated bounds or default values.
263  ///
264  /// # Arguments
265  ///
266  /// * `generics` - The `Generics` instance from which to extract parameter names.
267  ///
268  /// # Returns
269  ///
270  /// Returns an iterator over the names of the parameters.
271  ///
272  /// # Examples
273  ///
274  /// ```rust
275  /// # use macro_tools::syn::parse_quote;
276  ///
277  /// let generics : syn::Generics = parse_quote!
278  /// {
279  ///   < T : Clone + Default, U, 'a, const N : usize >
280  /// };
281  /// let names : Vec< _ > = macro_tools::generic_params::names( &generics ).collect();
282  ///
283  /// assert_eq!( names, vec!
284  /// [
285  ///   &syn::Ident::new( "T", proc_macro2::Span::call_site() ),
286  ///   &syn::Ident::new( "U", proc_macro2::Span::call_site() ),
287  ///   &syn::Ident::new( "a", proc_macro2::Span::call_site() ),
288  ///   &syn::Ident::new( "N", proc_macro2::Span::call_site() )
289  /// ]);
290  /// ```
291  #[ must_use ]
292  pub fn names( generics : &syn::Generics )
293  -> impl IterTrait< '_, &syn::Ident >
294  // -> std::iter::Map
295  // <
296  //   syn::punctuated::Iter< 'a, syn::GenericParam >,
297  //   impl FnMut( &'a syn::GenericParam ) -> &'a syn::Ident + 'a,
298  // >
299  {
300    generics.params.iter().map( | param | match param
301    {
302      syn::GenericParam::Type( type_param ) => &type_param.ident,
303      syn::GenericParam::Lifetime( lifetime_def ) => &lifetime_def.lifetime.ident,
304      syn::GenericParam::Const( const_param ) => &const_param.ident,
305    })
306  }
307
308  /// Decomposes `syn::Generics` into components suitable for different usage contexts in Rust implementations,
309  /// specifically focusing on different requirements for `impl` blocks and type definitions.
310  ///
311  /// This function prepares three versions of the generics:
312  /// - One preserving the full structure for `impl` declarations.
313  /// - One simplified for type definitions, removing bounds and defaults from type and const parameters, retaining only identifiers.
314  /// - One for the where clauses, if present, ensuring they are correctly punctuated.
315  ///
316  /// This helps in situations where you need different representations of generics for implementing traits,
317  /// defining types, or specifying trait bounds and conditions.
318  ///
319  /// This function is similar to `syn::Generics::split_for_impl`, which also splits generics into components
320  /// suitable for `impl` blocks and type definitions. However, `split_for_impl` wraps the tokens in `<>`, which
321  /// can reduce the flexibility of the results. The `decompose` function provides more control over the output
322  /// by not wrapping the tokens, allowing for more precise usage in macros and other contexts.
323  /// Additionally, `decompose` returns an extra component with the generics including defaults, which is often
324  /// in demand for certain macro or code generation tasks.
325  ///
326  /// # Examples
327  ///
328  /// ```rust
329  /// let code : syn::Generics = syn::parse_quote!{ <'a, T, const N : usize, U : Trait1> };
330  /// let ( generics_with_defaults, generics_for_impl, generics_for_ty, generics_where ) = macro_tools::generic_params::decompose( &code );
331  ///
332  /// // Use in a macro for generating code
333  /// macro_tools::qt!
334  /// {
335  ///   impl < #generics_for_impl > MyTrait for Struct1 < #generics_for_ty >
336  ///   where
337  ///     #generics_where
338  ///   {
339  ///     // implementation details...
340  ///   }
341  /// };
342  /// ```
343  ///
344  /// # Arguments
345  ///
346  /// * `generics` - A reference to the `syn::Generics` to be decomposed.
347  ///
348  /// # Returns
349  ///
350  /// Returns a tuple containing:
351  /// - `syn::punctuated::Punctuated<syn::GenericParam, syn::token::Comma>`: Original generics with defaults, used where full specification is needed.
352  /// - `syn::punctuated::Punctuated<syn::GenericParam, syn::token::Comma>`: Generics for `impl` blocks, retaining bounds but no defaults.
353  /// - `syn::punctuated::Punctuated<syn::GenericParam, syn::token::Comma>`: Simplified generics for type definitions, only identifiers.
354  /// - `syn::punctuated::Punctuated<syn::WherePredicate, syn::token::Comma>`: Where clauses, properly punctuated for use in where conditions.
355  ///
356  /// # Differences from `syn::Generics::split_for_impl`
357  ///
358  /// While both `decompose` and `split_for_impl` functions split generics into components for `impl` blocks, type definitions, and where clauses,
359  /// there are key differences:
360  /// - `split_for_impl` wraps the generics in `<>`, which can be limiting when you need to use the generics in a different context or format.
361  /// - `decompose` provides raw punctuated generic parameters, offering greater flexibility and control over the output format.
362  /// - `decompose` returns an extra component with the generics including defaults, which is often needed for certain macro or code generation tasks.
363  ///
364  /// # Example of function signature using `decompose`
365  ///
366  /// ```rust
367  /// use macro_tools::{ syn, proc_macro2, qt };
368  ///
369  /// fn generate_unit
370  /// (
371  ///   item_name : &syn::Ident,
372  ///   generics_with_defaults : syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >,
373  ///   generics_impl : syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >,
374  ///   generics_ty : syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >,
375  ///   generics_where: syn::punctuated::Punctuated< syn::WherePredicate, syn::token::Comma >,
376  /// )
377  /// -> proc_macro2::TokenStream
378  /// {
379  ///   qt!
380  ///   {
381  ///     #[ automatically_derived ]
382  ///     impl< #generics_impl > From< i32 > for #item_name< #generics_ty >
383  ///     where
384  ///       #generics_where
385  ///     {
386  ///       #[ inline ]
387  ///       fn from( src : i32 ) -> Self
388  ///       {
389  ///         Wrap( src )
390  ///       }
391  ///     }
392  ///   }
393  /// }
394  /// ```
395  ///
396  #[ allow( clippy::type_complexity ) ]
397  #[ must_use ]
398  pub fn decompose
399  (
400    generics : &syn::Generics,
401  )
402  ->
403  (
404    syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >,
405    syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >,
406    syn::punctuated::Punctuated< syn::GenericParam, syn::token::Comma >,
407    syn::punctuated::Punctuated< syn::WherePredicate, syn::token::Comma >,
408  )
409  {
410
411    let mut generics_with_defaults = generics.params.clone();
412    punctuated::ensure_trailing_comma( &mut generics_with_defaults );
413
414    let mut generics_for_impl = syn::punctuated::Punctuated::new();
415    let mut generics_for_ty = syn::punctuated::Punctuated::new();
416
417    // Process each generic parameter
418    for param in &generics.params
419    {
420      match param
421      {
422        syn::GenericParam::Type( type_param ) =>
423        {
424          // Retain bounds for generics_for_impl, remove defaults
425          let impl_param = syn::GenericParam::Type( syn::TypeParam
426          {
427            attrs : vec![],
428            ident : type_param.ident.clone(),
429            colon_token : type_param.colon_token,
430            bounds : type_param.bounds.clone(),
431            eq_token : None, // Remove default token
432            default : None, // Remove default value
433          } );
434          generics_for_impl.push_value( impl_param );
435          generics_for_impl.push_punct( syn::token::Comma::default() );
436
437          // Simplify for generics_for_ty by removing all except identifiers
438          let ty_param = syn::GenericParam::Type( syn::TypeParam
439          {
440            attrs : vec![],
441            ident : type_param.ident.clone(),
442            colon_token : None,
443            bounds : syn::punctuated::Punctuated::new(),
444            eq_token : None,
445            default : None,
446          } );
447          generics_for_ty.push_value( ty_param );
448          generics_for_ty.push_punct( syn::token::Comma::default() );
449        },
450        syn::GenericParam::Const( const_param ) =>
451        {
452          // Simplify const parameters by removing all details except the identifier
453          let impl_param = syn::GenericParam::Const( syn::ConstParam
454          {
455            attrs : vec![],
456            const_token : const_param.const_token,
457            ident : const_param.ident.clone(),
458            colon_token : const_param.colon_token,
459            ty : const_param.ty.clone(),
460            eq_token : None,
461            default : None,
462          } );
463          generics_for_impl.push_value( impl_param );
464          generics_for_impl.push_punct( syn::token::Comma::default() );
465
466          let ty_param = syn::GenericParam::Type( syn::TypeParam
467          {
468            attrs : vec![],
469            ident : const_param.ident.clone(),
470            colon_token : None,
471            bounds : syn::punctuated::Punctuated::new(),
472            eq_token : None,
473            default : None,
474          });
475          generics_for_ty.push_value( ty_param );
476          generics_for_ty.push_punct( syn::token::Comma::default() );
477        },
478        syn::GenericParam::Lifetime( lifetime_param ) =>
479        {
480          // Lifetimes are added as-is to generics_for_impl and without bounds to generics_for_ty
481          generics_for_impl.push_value( syn::GenericParam::Lifetime( lifetime_param.clone() ) );
482          generics_for_impl.push_punct( syn::token::Comma::default() );
483
484          let ty_param = syn::GenericParam::Lifetime( syn::LifetimeParam
485          {
486            attrs : vec![],
487            lifetime : lifetime_param.lifetime.clone(),
488            colon_token : None,
489            bounds : syn::punctuated::Punctuated::new(),
490          });
491          generics_for_ty.push_value( ty_param );
492          generics_for_ty.push_punct( syn::token::Comma::default() );
493        }
494      }
495    }
496
497    // Clone where predicates if present, ensuring they end with a comma
498    let generics_where = if let Some( where_clause ) = &generics.where_clause
499    {
500      let mut predicates = where_clause.predicates.clone();
501      punctuated::ensure_trailing_comma( &mut predicates );
502      predicates
503    }
504    else
505    {
506      syn::punctuated::Punctuated::new()
507    };
508
509    ( generics_with_defaults, generics_for_impl, generics_for_ty, generics_where )
510  }
511
512}
513
514#[ doc( inline ) ]
515#[ allow( unused_imports ) ]
516pub use own::*;
517
518#[ allow( unused_imports ) ]
519/// Own namespace of the module.
520pub mod own
521{
522  #[ allow( clippy::wildcard_imports ) ]
523  use super::*;
524
525  #[ doc( inline ) ]
526  pub use orphan::*;
527  #[ doc( inline ) ]
528  pub use private::
529  {
530    merge,
531    only_names,
532    names,
533    decompose,
534  };
535}
536
537/// Orphan namespace of the module.
538#[ allow( unused_imports ) ]
539pub mod orphan
540{
541  #[ allow( clippy::wildcard_imports ) ]
542  use super::*;
543  #[ doc( inline ) ]
544  pub use exposed::*;
545  #[ doc( inline ) ]
546  pub use private::
547  {
548    GenericsWithWhere,
549  };
550}
551
552/// Exposed namespace of the module.
553#[ allow( unused_imports ) ]
554pub mod exposed
555{
556  use super::*;
557  pub use super::super::generic_params;
558
559  #[ doc( inline ) ]
560  #[ allow( unused_imports ) ]
561  pub use super::
562  {
563    prelude::*,
564  };
565}
566
567/// Prelude to use essentials: `use my_module::prelude::*`.
568#[ allow( unused_imports ) ]
569pub mod prelude
570{
571  use super::*;
572}