Skip to main content

impl_tools_lib/
generics.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Custom version of [`syn`] generics supporting 'X: trait' bound
7
8use proc_macro2::TokenStream;
9use quote::{ToTokens, TokenStreamExt, quote};
10use syn::parse::{Parse, ParseStream, Result};
11use syn::punctuated::{Pair, Punctuated};
12use syn::token;
13use syn::{Attribute, ConstParam, LifetimeParam, PredicateLifetime};
14use syn::{BoundLifetimes, Ident, Lifetime, Token, Type};
15
16/// Lifetimes and type parameters attached an item
17///
18/// This is a custom variant of [`syn::Generics`]
19/// which supports `trait` as a parameter bound.
20#[derive(Debug)]
21pub struct Generics {
22    /// `<`
23    pub lt_token: Option<Token![<]>,
24    /// Parameters
25    pub params: Punctuated<GenericParam, Token![,]>,
26    /// `>`
27    pub gt_token: Option<Token![>]>,
28    /// `where` bounds
29    pub where_clause: Option<WhereClause>,
30}
31
32impl Default for Generics {
33    fn default() -> Self {
34        Generics {
35            lt_token: None,
36            params: Punctuated::new(),
37            gt_token: None,
38            where_clause: None,
39        }
40    }
41}
42
43/// A generic type parameter, lifetime, or const generic
44///
45/// This is a custom variant of [`syn::GenericParam`]
46/// which supports `trait` as a parameter bound.
47#[derive(Debug)]
48#[allow(clippy::large_enum_variant)]
49pub enum GenericParam {
50    /// Type parameter
51    Type(TypeParam),
52    /// Lifetime parameter
53    Lifetime(LifetimeParam),
54    /// `const` parameter
55    Const(ConstParam),
56}
57
58/// A generic type parameter: `T: Into<String>`.
59///
60/// This is a custom variant of [`syn::TypeParam`]
61/// which supports `trait` as a parameter bound.
62#[derive(Debug)]
63pub struct TypeParam {
64    /// Type parameter attributes
65    pub attrs: Vec<Attribute>,
66    /// Type parameter identifier
67    pub ident: Ident,
68    /// `:`
69    pub colon_token: Option<Token![:]>,
70    /// List of type bounds
71    pub bounds: Punctuated<TypeParamBound, Token![+]>,
72    /// Optional default value
73    pub default: Option<(Token![=], Type)>,
74}
75
76/// A trait or lifetime used as a bound on a type parameter.
77///
78/// This is a superset of [`syn::TypeParamBound`].
79#[derive(Debug)]
80pub enum TypeParamBound {
81    /// `trait` used as a bound (substituted for the trait name by [`ToTokensSubst`])
82    TraitSubst(Token![trait]),
83    /// Everything else
84    Other(syn::TypeParamBound),
85}
86
87/// A `where` clause in a definition: `where T: Deserialize<'de>, D: 'static`.
88///
89/// This is a custom variant of [`syn::WhereClause`]
90/// which supports `trait` as a parameter bound.
91#[derive(Debug)]
92pub struct WhereClause {
93    /// `where`
94    pub where_token: Token![where],
95    /// Parameter bounds
96    pub predicates: Punctuated<WherePredicate, Token![,]>,
97}
98
99/// A single predicate in a `where` clause: `T: Deserialize<'de>`.
100///
101/// This is a custom variant of [`syn::WherePredicate`]
102/// which supports `trait` as a parameter bound.
103#[allow(clippy::large_enum_variant)]
104#[derive(Debug)]
105pub enum WherePredicate {
106    /// A type predicate in a `where` clause: `for<'c> Foo<'c>: Trait<'c>`.
107    Type(PredicateType),
108
109    /// A lifetime predicate in a `where` clause: `'a: 'b + 'c`.
110    Lifetime(PredicateLifetime),
111}
112
113/// A type predicate in a `where` clause: `for<'c> Foo<'c>: Trait<'c>`.
114///
115/// This is a custom variant of [`syn::PredicateType`]
116/// which supports `trait` as a parameter bound.
117#[derive(Debug)]
118pub struct PredicateType {
119    /// Any lifetimes from a `for` binding
120    pub lifetimes: Option<BoundLifetimes>,
121    /// The type being bounded
122    pub bounded_ty: Type,
123    /// `:` before bounds
124    pub colon_token: Token![:],
125    /// Trait and lifetime bounds (`Clone+Send+'static`)
126    pub bounds: Punctuated<TypeParamBound, Token![+]>,
127}
128
129mod parsing {
130    use super::*;
131    use syn::ext::IdentExt;
132
133    impl Parse for Generics {
134        fn parse(input: ParseStream) -> Result<Self> {
135            if !input.peek(Token![<]) {
136                return Ok(Generics::default());
137            }
138
139            let lt_token: Token![<] = input.parse()?;
140
141            let mut params = Punctuated::new();
142            loop {
143                if input.peek(Token![>]) {
144                    break;
145                }
146
147                let attrs = input.call(Attribute::parse_outer)?;
148                let lookahead = input.lookahead1();
149                if lookahead.peek(Lifetime) {
150                    params.push_value(GenericParam::Lifetime(LifetimeParam {
151                        attrs,
152                        ..input.parse()?
153                    }));
154                } else if lookahead.peek(Ident) {
155                    params.push_value(GenericParam::Type(TypeParam {
156                        attrs,
157                        ..input.parse()?
158                    }));
159                } else if lookahead.peek(Token![const]) {
160                    params.push_value(GenericParam::Const(ConstParam {
161                        attrs,
162                        ..input.parse()?
163                    }));
164                } else if input.peek(Token![_]) {
165                    params.push_value(GenericParam::Type(TypeParam {
166                        attrs,
167                        ident: input.call(Ident::parse_any)?,
168                        colon_token: None,
169                        bounds: Punctuated::new(),
170                        default: None,
171                    }));
172                } else {
173                    return Err(lookahead.error());
174                }
175
176                if input.peek(Token![>]) {
177                    break;
178                }
179                let punct = input.parse()?;
180                params.push_punct(punct);
181            }
182
183            let gt_token: Token![>] = input.parse()?;
184
185            Ok(Generics {
186                lt_token: Some(lt_token),
187                params,
188                gt_token: Some(gt_token),
189                where_clause: None,
190            })
191        }
192    }
193
194    impl Parse for TypeParam {
195        fn parse(input: ParseStream) -> Result<Self> {
196            let attrs = input.call(Attribute::parse_outer)?;
197            let ident: Ident = input.parse()?;
198            let colon_token: Option<Token![:]> = input.parse()?;
199
200            let mut bounds = Punctuated::new();
201            if colon_token.is_some() {
202                loop {
203                    if input.peek(Token![,]) || input.peek(Token![>]) || input.peek(Token![=]) {
204                        break;
205                    }
206                    let value: TypeParamBound = input.parse()?;
207                    bounds.push_value(value);
208                    if !input.peek(Token![+]) {
209                        break;
210                    }
211                    let punct: Token![+] = input.parse()?;
212                    bounds.push_punct(punct);
213                }
214            }
215
216            let eq_token: Option<Token![=]> = input.parse()?;
217            let default = if let Some(tok) = eq_token {
218                Some((tok, input.parse::<Type>()?))
219            } else {
220                None
221            };
222
223            Ok(TypeParam {
224                attrs,
225                ident,
226                colon_token,
227                bounds,
228                default,
229            })
230        }
231    }
232
233    impl Parse for TypeParamBound {
234        fn parse(input: ParseStream) -> Result<Self> {
235            if input.peek(Token![trait]) {
236                input.parse().map(TypeParamBound::TraitSubst)
237            } else {
238                syn::TypeParamBound::parse(input).map(TypeParamBound::Other)
239            }
240        }
241    }
242
243    impl Parse for WhereClause {
244        fn parse(input: ParseStream) -> Result<Self> {
245            Ok(WhereClause {
246                where_token: input.parse()?,
247                predicates: {
248                    let mut predicates = Punctuated::new();
249                    loop {
250                        if input.is_empty()
251                            || input.peek(token::Brace)
252                            || input.peek(Token![,])
253                            || input.peek(Token![;])
254                            || input.peek(Token![:]) && !input.peek(Token![::])
255                            || input.peek(Token![=])
256                        {
257                            break;
258                        }
259                        let value = input.parse()?;
260                        predicates.push_value(value);
261                        if !input.peek(Token![,]) {
262                            break;
263                        }
264                        let punct = input.parse()?;
265                        predicates.push_punct(punct);
266                    }
267                    predicates
268                },
269            })
270        }
271    }
272
273    impl Parse for WherePredicate {
274        fn parse(input: ParseStream) -> Result<Self> {
275            if input.peek(Lifetime) && input.peek2(Token![:]) {
276                Ok(WherePredicate::Lifetime(PredicateLifetime {
277                    attrs: vec![],
278                    lifetime: input.parse()?,
279                    colon_token: input.parse()?,
280                    bounds: {
281                        let mut bounds = Punctuated::new();
282                        loop {
283                            if input.is_empty()
284                                || input.peek(token::Brace)
285                                || input.peek(Token![,])
286                                || input.peek(Token![;])
287                                || input.peek(Token![:])
288                                || input.peek(Token![=])
289                            {
290                                break;
291                            }
292                            let value = input.parse()?;
293                            bounds.push_value(value);
294                            if !input.peek(Token![+]) {
295                                break;
296                            }
297                            let punct = input.parse()?;
298                            bounds.push_punct(punct);
299                        }
300                        bounds
301                    },
302                }))
303            } else {
304                Ok(WherePredicate::Type(PredicateType {
305                    lifetimes: input.parse()?,
306                    bounded_ty: input.parse()?,
307                    colon_token: input.parse()?,
308                    bounds: {
309                        let mut bounds = Punctuated::new();
310                        loop {
311                            if input.is_empty()
312                                || input.peek(token::Brace)
313                                || input.peek(Token![,])
314                                || input.peek(Token![;])
315                                || input.peek(Token![:]) && !input.peek(Token![::])
316                                || input.peek(Token![=])
317                            {
318                                break;
319                            }
320                            let value = input.parse()?;
321                            bounds.push_value(value);
322                            if !input.peek(Token![+]) {
323                                break;
324                            }
325                            let punct = input.parse()?;
326                            bounds.push_punct(punct);
327                        }
328                        bounds
329                    },
330                }))
331            }
332        }
333    }
334}
335
336/// Tokenization trait with substitution
337///
338/// This is similar to [`quote::ToTokens`], but replaces instances of `trait`
339/// as a parameter bound with `subst`.
340pub trait ToTokensSubst {
341    /// Write `self` to the given [`TokenStream`]
342    fn to_tokens_subst(&self, tokens: &mut TokenStream, subst: &TokenStream);
343}
344
345mod printing_subst {
346    use super::*;
347    use syn::AttrStyle;
348
349    impl ToTokensSubst for Generics {
350        fn to_tokens_subst(&self, tokens: &mut TokenStream, subst: &TokenStream) {
351            if self.params.is_empty() {
352                return;
353            }
354
355            self.lt_token.unwrap_or_default().to_tokens(tokens);
356
357            let mut trailing_or_empty = true;
358            for pair in self.params.pairs() {
359                if let GenericParam::Lifetime(param) = *pair.value() {
360                    param.to_tokens(tokens);
361                    pair.punct().to_tokens(tokens);
362                    trailing_or_empty = pair.punct().is_some();
363                }
364            }
365            for pair in self.params.pairs() {
366                match *pair.value() {
367                    GenericParam::Type(param) => {
368                        if !trailing_or_empty {
369                            <Token![,]>::default().to_tokens(tokens);
370                            trailing_or_empty = true;
371                        }
372                        param.to_tokens_subst(tokens, subst);
373                        pair.punct().to_tokens(tokens);
374                    }
375                    GenericParam::Const(param) => {
376                        if !trailing_or_empty {
377                            <Token![,]>::default().to_tokens(tokens);
378                            trailing_or_empty = true;
379                        }
380                        param.to_tokens(tokens);
381                        pair.punct().to_tokens(tokens);
382                    }
383                    GenericParam::Lifetime(_) => {}
384                }
385            }
386
387            self.gt_token.unwrap_or_default().to_tokens(tokens);
388        }
389    }
390
391    impl ToTokensSubst for TypeParam {
392        fn to_tokens_subst(&self, tokens: &mut TokenStream, subst: &TokenStream) {
393            tokens.append_all(
394                self.attrs
395                    .iter()
396                    .filter(|attr| matches!(attr.style, AttrStyle::Outer)),
397            );
398            self.ident.to_tokens(tokens);
399            if !self.bounds.is_empty() {
400                self.colon_token.unwrap_or_default().to_tokens(tokens);
401                self.bounds.to_tokens_subst(tokens, subst);
402            }
403            if let Some((tok, default)) = &self.default {
404                tok.to_tokens(tokens);
405                default.to_tokens(tokens);
406            }
407        }
408    }
409
410    impl ToTokensSubst for WhereClause {
411        fn to_tokens_subst(&self, tokens: &mut TokenStream, subst: &TokenStream) {
412            if !self.predicates.is_empty() {
413                self.where_token.to_tokens(tokens);
414                self.predicates.to_tokens_subst(tokens, subst);
415            }
416        }
417    }
418
419    impl<T, P> ToTokensSubst for Punctuated<T, P>
420    where
421        T: ToTokensSubst,
422        P: ToTokens,
423    {
424        fn to_tokens_subst(&self, tokens: &mut TokenStream, subst: &TokenStream) {
425            for pair in self.pairs() {
426                pair.value().to_tokens_subst(tokens, subst);
427                if let Some(punct) = pair.punct() {
428                    punct.to_tokens(tokens);
429                }
430            }
431        }
432    }
433
434    impl ToTokensSubst for WherePredicate {
435        fn to_tokens_subst(&self, tokens: &mut TokenStream, subst: &TokenStream) {
436            match self {
437                WherePredicate::Type(ty) => ty.to_tokens_subst(tokens, subst),
438                WherePredicate::Lifetime(lt) => lt.to_tokens(tokens),
439            }
440        }
441    }
442
443    impl ToTokensSubst for PredicateType {
444        fn to_tokens_subst(&self, tokens: &mut TokenStream, subst: &TokenStream) {
445            self.lifetimes.to_tokens(tokens);
446            self.bounded_ty.to_tokens(tokens);
447            self.colon_token.to_tokens(tokens);
448            self.bounds.to_tokens_subst(tokens, subst);
449        }
450    }
451
452    impl ToTokensSubst for TypeParamBound {
453        fn to_tokens_subst(&self, tokens: &mut TokenStream, subst: &TokenStream) {
454            match self {
455                TypeParamBound::TraitSubst(_) => tokens.append_all(quote! { #subst }),
456                TypeParamBound::Other(bound) => bound.to_tokens(tokens),
457            }
458        }
459    }
460}
461
462fn map_type_param_bound(bound: &syn::TypeParamBound) -> TypeParamBound {
463    TypeParamBound::Other(bound.clone())
464}
465
466fn map_generic_param(param: &syn::GenericParam) -> GenericParam {
467    match param {
468        syn::GenericParam::Type(ty) => GenericParam::Type(TypeParam {
469            attrs: ty.attrs.clone(),
470            ident: ty.ident.clone(),
471            colon_token: ty.colon_token,
472            bounds: Punctuated::from_iter(
473                ty.bounds
474                    .pairs()
475                    .map(|pair| map_pair(pair, map_type_param_bound)),
476            ),
477            default: ty.default.clone(),
478        }),
479        syn::GenericParam::Lifetime(lt) => GenericParam::Lifetime(lt.clone()),
480        syn::GenericParam::Const(c) => GenericParam::Const(c.clone()),
481    }
482}
483
484fn map_pair<U, V, P: Clone, F: Fn(U) -> V>(pair: Pair<U, &P>, f: F) -> Pair<V, P> {
485    match pair {
486        Pair::Punctuated(value, punct) => Pair::Punctuated(f(value), punct.clone()),
487        Pair::End(value) => Pair::End(f(value)),
488    }
489}
490
491impl Generics {
492    /// Generate (`impl_generics`, `where_clause`) tokens
493    ///
494    /// Combines generics from `self` and `item_generics`.
495    ///
496    /// This is the equivalent of the first and third items output by
497    /// [`syn::Generics::split_for_impl`]. Any instance of `trait` as a parameter
498    /// bound is replaced by `subst`.
499    ///
500    /// Note: use `ty_generics` from [`syn::Generics::split_for_impl`] or
501    /// [`Self::ty_generics`] as appropriate.
502    pub fn impl_generics(
503        mut self,
504        item_generics: &syn::Generics,
505        subst: &TokenStream,
506    ) -> (TokenStream, TokenStream) {
507        let mut impl_generics = quote! {};
508        if self.params.is_empty() {
509            item_generics.to_tokens(&mut impl_generics);
510        } else {
511            if !self.params.empty_or_trailing() {
512                self.params.push_punct(Default::default());
513            }
514            self.params.extend(
515                item_generics
516                    .params
517                    .pairs()
518                    .map(|pair| map_pair(pair, map_generic_param)),
519            );
520            self.to_tokens_subst(&mut impl_generics, subst);
521        }
522
523        let where_clause = clause_to_toks(
524            &self.where_clause,
525            item_generics.where_clause.as_ref(),
526            subst,
527        );
528        (impl_generics, where_clause)
529    }
530    /// Generate `ty_generics` tokens
531    ///
532    /// Combines generics from `self` and `item_generics`.
533    ///
534    /// This is the equivalent to the second item output by
535    /// [`syn::Generics::split_for_impl`].
536    pub fn ty_generics(&self, item_generics: &syn::Generics) -> TokenStream {
537        let mut toks = TokenStream::new();
538        let tokens = &mut toks;
539
540        if self.params.is_empty() {
541            let (_, ty_generics, _) = item_generics.split_for_impl();
542            ty_generics.to_tokens(tokens);
543            return toks;
544        }
545
546        self.lt_token.unwrap_or_default().to_tokens(tokens);
547
548        // Print lifetimes before types and consts (see syn impl)
549        for (def, punct) in self
550            .params
551            .pairs()
552            .filter_map(|param| {
553                if let GenericParam::Lifetime(def) = *param.value() {
554                    Some((def, param.punct().map(|p| **p).unwrap_or_default()))
555                } else {
556                    None
557                }
558            })
559            .chain(item_generics.params.pairs().filter_map(|param| {
560                if let syn::GenericParam::Lifetime(def) = *param.value() {
561                    Some((def, param.punct().map(|p| **p).unwrap_or_default()))
562                } else {
563                    None
564                }
565            }))
566        {
567            // Leave off the lifetime bounds and attributes
568            def.lifetime.to_tokens(tokens);
569            punct.to_tokens(tokens);
570        }
571
572        for param in self.params.pairs() {
573            match *param.value() {
574                GenericParam::Lifetime(_) => continue,
575                GenericParam::Type(param) => {
576                    // Leave off the type parameter defaults
577                    param.ident.to_tokens(tokens);
578                }
579                GenericParam::Const(param) => {
580                    // Leave off the const parameter defaults
581                    param.ident.to_tokens(tokens);
582                }
583            }
584            param
585                .punct()
586                .map(|p| **p)
587                .unwrap_or_default()
588                .to_tokens(tokens);
589        }
590        for param in item_generics.params.pairs() {
591            match *param.value() {
592                syn::GenericParam::Lifetime(_) => continue,
593                syn::GenericParam::Type(param) => {
594                    // Leave off the type parameter defaults
595                    param.ident.to_tokens(tokens);
596                }
597                syn::GenericParam::Const(param) => {
598                    // Leave off the const parameter defaults
599                    param.ident.to_tokens(tokens);
600                }
601            }
602            param
603                .punct()
604                .map(|p| **p)
605                .unwrap_or_default()
606                .to_tokens(tokens);
607        }
608
609        self.gt_token.unwrap_or_default().to_tokens(tokens);
610
611        toks
612    }
613}
614
615/// Generate a `where_clause`
616///
617/// This merges a [`WhereClause`] with a [`syn::WhereClause`], replacing any
618/// instance of `trait` as a parameter bound in `wc` with `subst`.
619pub fn clause_to_toks(
620    wc: &Option<WhereClause>,
621    item_wc: Option<&syn::WhereClause>,
622    subst: &TokenStream,
623) -> TokenStream {
624    match (wc, item_wc) {
625        (None, None) => quote! {},
626        (Some(wc), None) => {
627            let mut toks = quote! {};
628            wc.to_tokens_subst(&mut toks, subst);
629            toks
630        }
631        (None, Some(wc)) => quote! { #wc },
632        (Some(wc), Some(item_wc)) => {
633            let mut toks = quote! { #item_wc };
634            if !item_wc.predicates.empty_or_trailing() {
635                toks.append_all(quote! { , });
636            }
637            wc.predicates.to_tokens_subst(&mut toks, subst);
638            toks
639        }
640    }
641}