as_derive_utils/
gen_params_in.rs

1//! Contains the `GenParamsIn` type,for printing generic parameters.
2
3use syn::{
4    token::{Colon, Comma, Const, Star},
5    GenericParam, Generics,
6};
7
8use proc_macro2::TokenStream;
9use quote::{quote, ToTokens};
10
11use crate::utils::NoTokens;
12
13/// Used to print (with ToTokens) the generc parameter differently depending on where
14/// it is being used/declared.
15///
16/// One can also add stuff to be printed after lifetime/type parameters.
17#[derive(Debug, Copy, Clone)]
18pub struct GenParamsIn<'a, AL = NoTokens> {
19    pub generics: &'a Generics,
20    /// Where the generic parameters are being printed.
21    pub in_what: InWhat,
22    /// Whether type parameters are all `?Sized`
23    pub unsized_types: bool,
24    /// Whether type bounds will be printed in type parameter declarations.
25    pub with_bounds: bool,
26    skip_lifetimes: bool,
27    /// What will be printed after lifetime parameters
28    after_lifetimes: Option<AL>,
29    /// What will be printed after type parameters
30    after_types: Option<AL>,
31    skip_consts: bool,
32    skip_unbounded: bool,
33}
34
35/// Where the generic parameters are being printed.
36#[derive(Debug, Copy, Clone, PartialEq, Eq)]
37pub enum InWhat {
38    /// For the header of an impl block,as in the contents of `impl< ... >`.
39    ImplHeader,
40    /// For trait/struct/enum/union
41    ItemDecl,
42    /// For when using a generic as an argument for a trait/struct/enum/union.
43    ItemUse,
44    /// For defining the fields of a Dummy struct that is never instantiated,
45    /// only using its associated items.
46    DummyStruct,
47}
48
49impl<'a> GenParamsIn<'a> {
50    #[allow(dead_code)]
51    pub fn new(generics: &'a Generics, in_what: InWhat) -> Self {
52        Self {
53            generics,
54            in_what,
55            unsized_types: false,
56            with_bounds: true,
57            skip_lifetimes: false,
58            after_lifetimes: None,
59            after_types: None,
60            skip_consts: false,
61            skip_unbounded: false,
62        }
63    }
64}
65
66impl<'a, AL> GenParamsIn<'a, AL> {
67    /// Constructs a GenParamsIn that prints `after lifetimes` after lifetime parameters.
68    pub fn with_after_lifetimes(
69        generics: &'a Generics,
70        in_what: InWhat,
71        after_lifetimes: AL,
72    ) -> Self {
73        Self {
74            generics,
75            in_what,
76            unsized_types: false,
77            with_bounds: true,
78            skip_lifetimes: false,
79            after_lifetimes: Some(after_lifetimes),
80            after_types: None,
81            skip_consts: false,
82            skip_unbounded: false,
83        }
84    }
85    /// Constructs a GenParamsIn that prints `after types` after type parameters.
86    pub fn with_after_types(generics: &'a Generics, in_what: InWhat, after_types: AL) -> Self {
87        Self {
88            generics,
89            in_what,
90            unsized_types: false,
91            with_bounds: true,
92            skip_lifetimes: false,
93            after_lifetimes: None,
94            after_types: Some(after_types),
95            skip_consts: false,
96            skip_unbounded: false,
97        }
98    }
99    /// Removes the bounds on type parameters on type parameter declarations.
100    pub fn set_no_bounds(&mut self) {
101        self.with_bounds = false;
102    }
103    /// Makes all type parameters `?Sized`.
104    #[allow(dead_code)]
105    pub fn set_unsized_types(&mut self) {
106        self.unsized_types = true;
107    }
108
109    pub fn skip_lifetimes(&mut self) {
110        self.skip_lifetimes = true;
111    }
112
113    pub fn skip_consts(&mut self) {
114        self.skip_consts = true;
115    }
116    pub fn skip_unbounded(&mut self) {
117        self.skip_unbounded = true;
118        self.skip_consts();
119        self.skip_lifetimes();
120    }
121
122    pub fn skips_unbounded(&self) -> bool {
123        self.skip_unbounded
124    }
125
126    /// Queries whether bounds are printed.
127    pub fn outputs_bounds(&self) -> bool {
128        self.with_bounds && matches!(self.in_what, InWhat::ImplHeader | InWhat::ItemDecl)
129    }
130
131    /// Queries whether all types have a `?Sized` bound.
132    pub fn are_types_unsized(&self) -> bool {
133        self.unsized_types && matches!(self.in_what, InWhat::ItemDecl | InWhat::ImplHeader)
134    }
135}
136
137impl<'a, AL> ToTokens for GenParamsIn<'a, AL>
138where
139    AL: ToTokens,
140{
141    fn to_tokens(&self, ts: &mut TokenStream) {
142        let with_bounds = self.outputs_bounds();
143
144        let with_default = self.in_what == InWhat::ItemDecl;
145
146        let in_dummy_struct = self.in_what == InWhat::DummyStruct;
147
148        let unsized_types = self.are_types_unsized();
149
150        let mut iter = self.generics.params.iter().peekable();
151
152        let comma = Comma::default();
153        let brace = syn::token::Brace::default();
154
155        if self.skip_lifetimes {
156            while let Some(GenericParam::Lifetime { .. }) = iter.peek() {
157                iter.next();
158            }
159        } else {
160            while let Some(GenericParam::Lifetime(gen)) = iter.peek() {
161                iter.next();
162
163                if in_dummy_struct {
164                    syn::token::And::default().to_tokens(ts);
165                    gen.lifetime.to_tokens(ts);
166                    syn::token::Paren::default().surround(ts, |_| ());
167                } else {
168                    gen.lifetime.to_tokens(ts);
169                    if with_bounds {
170                        gen.colon_token.to_tokens(ts);
171                        gen.bounds.to_tokens(ts);
172                    }
173                }
174
175                comma.to_tokens(ts);
176            }
177        }
178
179        self.after_lifetimes.to_tokens(ts);
180
181        while let Some(GenericParam::Type(gen)) = iter.peek() {
182            iter.next();
183            if gen.bounds.is_empty() && self.skip_unbounded {
184                continue;
185            }
186
187            if in_dummy_struct {
188                Star::default().to_tokens(ts);
189                Const::default().to_tokens(ts);
190            }
191            gen.ident.to_tokens(ts);
192
193            if (with_bounds && gen.colon_token.is_some()) || unsized_types {
194                Colon::default().to_tokens(ts);
195                if unsized_types {
196                    quote!(?Sized+).to_tokens(ts);
197                }
198                if with_bounds {
199                    gen.bounds.to_tokens(ts);
200                }
201            }
202
203            if with_default {
204                gen.eq_token.to_tokens(ts);
205                gen.default.to_tokens(ts);
206            }
207
208            comma.to_tokens(ts);
209        }
210
211        self.after_types.to_tokens(ts);
212
213        if !in_dummy_struct && !self.skip_consts {
214            while let Some(GenericParam::Const(gen)) = iter.peek() {
215                iter.next();
216
217                if self.in_what != InWhat::ItemUse {
218                    gen.const_token.to_tokens(ts);
219                }
220                if self.in_what == InWhat::ItemUse {
221                    // Have to surround the const parameter when it's used,
222                    // because otherwise it's interpreted as a type parameter.
223                    // Remove this branch once outputting the identifier
224                    // for the const parameter just works.
225                    brace.surround(ts, |ts| {
226                        gen.ident.to_tokens(ts);
227                    });
228                } else {
229                    gen.ident.to_tokens(ts);
230                }
231                if self.in_what != InWhat::ItemUse {
232                    Colon::default().to_tokens(ts);
233                    gen.ty.to_tokens(ts);
234                }
235                if with_default {
236                    gen.eq_token.to_tokens(ts);
237                    gen.default.to_tokens(ts);
238                }
239
240                comma.to_tokens(ts);
241            }
242        }
243    }
244}