1use crate::{GenericParam, GenericParams, LifetimeName, ToTokens, TokenStream};
2use quote::quote;
3
4#[derive(Clone)]
6pub enum GenericParamName {
7 Lifetime(LifetimeName),
9
10 Type(TokenStream),
12
13 Const(TokenStream),
15}
16
17#[derive(Clone)]
19pub struct BoundedGenericParam {
20 pub param: GenericParamName,
22
23 pub bounds: Option<TokenStream>,
25}
26
27#[derive(Clone)]
32pub struct BoundedGenericParams {
33 pub params: Vec<BoundedGenericParam>,
35}
36
37pub struct WithBounds<'a>(&'a BoundedGenericParams);
46
47pub struct WithoutBounds<'a>(&'a BoundedGenericParams);
56
57pub struct AsPhantomData<'a>(&'a BoundedGenericParams);
67
68impl quote::ToTokens for AsPhantomData<'_> {
69 fn to_tokens(&self, tokens: &mut TokenStream) {
70 if self.0.params.len() == 1
73 && let GenericParamName::Lifetime(name) = &self.0.params[0].param
74 {
75 tokens.extend(quote! { 𝟋Ph<#name> });
76 return;
77 }
78
79 let mut temp = TokenStream::new();
81
82 {
83 #[expect(unused)]
84 let tokens = ();
85
86 let mut first_param = true;
88
89 for param in &self.0.params {
91 if !first_param {
92 temp.extend(quote! { , });
93 }
94
95 match ¶m.param {
96 GenericParamName::Lifetime(name) => {
97 temp.extend(quote! { *mut &#name () });
98 }
99 GenericParamName::Type(name) => {
100 temp.extend(quote! { #name });
101 }
102 GenericParamName::Const(name) => {
103 temp.extend(quote! { [u32; #name] });
104 }
105 }
106
107 first_param = false;
108 }
109
110 if first_param {
112 temp.extend(quote! { () });
113 }
114 }
115 tokens.extend(quote! {
116 ::core::marker::PhantomData<(#temp)>
117 })
118 }
119}
120
121impl BoundedGenericParams {
122 pub fn as_phantom_data(&self) -> AsPhantomData<'_> {
124 AsPhantomData(self)
125 }
126}
127
128impl quote::ToTokens for WithBounds<'_> {
129 fn to_tokens(&self, tokens: &mut TokenStream) {
130 if self.0.params.is_empty() {
131 return;
132 }
133
134 tokens.extend(quote! {
135 <
136 });
137
138 for (i, param) in self.0.params.iter().enumerate() {
139 if i > 0 {
140 tokens.extend(quote! { , });
141 }
142
143 match ¶m.param {
144 GenericParamName::Lifetime(name) => {
145 tokens.extend(quote! { #name });
146 }
147 GenericParamName::Type(name) => {
148 tokens.extend(quote! { #name });
149 }
150 GenericParamName::Const(name) => {
151 tokens.extend(quote! { const #name });
152 }
153 }
154
155 if let Some(bounds) = ¶m.bounds {
157 tokens.extend(quote! { : #bounds });
158 }
159 }
160
161 tokens.extend(quote! {
162 >
163 });
164 }
165}
166
167impl quote::ToTokens for WithoutBounds<'_> {
168 fn to_tokens(&self, tokens: &mut TokenStream) {
169 if self.0.params.is_empty() {
170 return;
171 }
172
173 tokens.extend(quote! {
174 <
175 });
176
177 for (i, param) in self.0.params.iter().enumerate() {
178 if i > 0 {
179 tokens.extend(quote! { , });
180 }
181
182 match ¶m.param {
183 GenericParamName::Lifetime(name) => {
184 tokens.extend(quote! { #name });
185 }
186 GenericParamName::Type(name) => {
187 tokens.extend(quote! { #name });
188 }
189 GenericParamName::Const(name) => {
190 tokens.extend(quote! { #name });
191 }
192 }
193 }
194
195 tokens.extend(quote! {
196 >
197 });
198 }
199}
200
201impl BoundedGenericParams {
202 pub fn display_with_bounds(&self) -> WithBounds<'_> {
204 WithBounds(self)
205 }
206
207 pub fn display_without_bounds(&self) -> WithoutBounds<'_> {
209 WithoutBounds(self)
210 }
211
212 pub fn display_as_phantom_data(&self) -> AsPhantomData<'_> {
223 AsPhantomData(self)
224 }
225
226 pub fn with(&self, param: BoundedGenericParam) -> Self {
228 let mut params = self.params.clone();
229
230 match ¶m.param {
231 GenericParamName::Lifetime(_) => {
232 let insert_position = params
234 .iter()
235 .position(|p| !matches!(p.param, GenericParamName::Lifetime(_)))
236 .unwrap_or(params.len());
237
238 params.insert(insert_position, param);
239 }
240 GenericParamName::Type(_) => {
241 let after_lifetimes = params
243 .iter()
244 .position(|p| !matches!(p.param, GenericParamName::Lifetime(_)))
245 .unwrap_or(params.len());
246
247 let insert_position = params[after_lifetimes..]
248 .iter()
249 .position(|p| matches!(p.param, GenericParamName::Const(_)))
250 .map(|pos| pos + after_lifetimes)
251 .unwrap_or(params.len());
252
253 params.insert(insert_position, param);
254 }
255 GenericParamName::Const(_) => {
256 params.push(param);
258 }
259 }
260
261 Self { params }
262 }
263
264 pub fn with_lifetime(&self, name: LifetimeName) -> Self {
269 self.with(BoundedGenericParam {
270 param: GenericParamName::Lifetime(name),
271 bounds: None,
272 })
273 }
274
275 pub fn with_type(&self, name: TokenStream) -> Self {
280 self.with(BoundedGenericParam {
281 param: GenericParamName::Type(name),
282 bounds: None,
283 })
284 }
285}
286
287impl BoundedGenericParams {
288 pub fn parse(generics: Option<&GenericParams>) -> Self {
298 let Some(generics) = generics else {
299 return Self { params: Vec::new() };
300 };
301
302 let mut params = Vec::new();
303
304 for param in generics.params.iter() {
305 match ¶m.value {
306 GenericParam::Type {
307 name,
308 bounds,
309 default: _,
310 } => {
311 params.push(BoundedGenericParam {
312 param: GenericParamName::Type(name.to_token_stream()),
313 bounds: bounds
314 .as_ref()
315 .map(|bounds| bounds.second.to_token_stream()),
316 });
317 }
318 GenericParam::Lifetime { name, bounds } => {
319 params.push(BoundedGenericParam {
320 param: GenericParamName::Lifetime(LifetimeName(name.name.clone())),
321 bounds: bounds
322 .as_ref()
323 .map(|bounds| bounds.second.to_token_stream()),
324 });
325 }
326 GenericParam::Const {
327 _const: _,
328 name,
329 _colon: _,
330 typ,
331 default: _,
332 } => {
333 params.push(BoundedGenericParam {
334 param: GenericParamName::Const(name.to_token_stream()),
335 bounds: Some(typ.to_token_stream()),
336 });
337 }
338 }
339 }
340
341 Self { params }
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use super::{BoundedGenericParam, BoundedGenericParams, GenericParamName};
348 use crate::LifetimeName;
349 use quote::{ToTokens as _, quote};
350
351 fn render_to_string<T: quote::ToTokens>(t: T) -> String {
353 quote!(#t).to_string()
354 }
355
356 #[test]
357 fn test_empty_generic_params() {
358 let p = BoundedGenericParams { params: vec![] };
359 assert_eq!(render_to_string(p.display_with_bounds()), "");
360 assert_eq!(render_to_string(p.display_without_bounds()), "");
361 }
362
363 #[test]
364 fn print_multiple_generic_params() {
365 let p = BoundedGenericParams {
366 params: vec![
367 BoundedGenericParam {
368 bounds: Some(quote! { 'static }),
369 param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("a"))),
370 },
371 BoundedGenericParam {
372 bounds: Some(quote! { Clone + Debug }),
373 param: GenericParamName::Type(quote! { T }),
374 },
375 BoundedGenericParam {
376 bounds: None,
377 param: GenericParamName::Type(quote! { U }),
378 },
379 BoundedGenericParam {
380 bounds: Some(quote! { usize }), param: GenericParamName::Const(quote! { N }),
382 },
383 ],
384 };
385 let expected_with_bounds = quote! { <'a : 'static, T : Clone + Debug, U, const N : usize> };
387 assert_eq!(
388 p.display_with_bounds().to_token_stream().to_string(),
389 expected_with_bounds.to_string()
390 );
391
392 let expected_without_bounds = quote! { <'a, T, U, N> }; assert_eq!(
395 p.display_without_bounds().to_token_stream().to_string(),
396 expected_without_bounds.to_string()
397 );
398 }
399
400 #[test]
401 fn test_add_mixed_parameters() {
402 let mut params = BoundedGenericParams { params: vec![] };
404
405 params = params.with(BoundedGenericParam {
407 bounds: None,
408 param: GenericParamName::Type(quote! { T }),
409 });
410
411 params = params.with(BoundedGenericParam {
412 bounds: Some(quote! { usize }), param: GenericParamName::Const(quote! { N }),
414 });
415
416 params = params.with(BoundedGenericParam {
417 bounds: None,
418 param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("a"))),
419 });
420
421 params = params.with(BoundedGenericParam {
422 bounds: Some(quote! { Clone }),
423 param: GenericParamName::Type(quote! { U }),
424 });
425
426 params = params.with(BoundedGenericParam {
427 bounds: Some(quote! { 'static }),
428 param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("b"))),
429 });
430
431 params = params.with(BoundedGenericParam {
432 bounds: Some(quote! { u8 }), param: GenericParamName::Const(quote! { M }),
434 });
435
436 let expected_without_bounds = quote! { <'a, 'b, T, U, N, M> };
438 assert_eq!(
440 params
441 .display_without_bounds()
442 .to_token_stream()
443 .to_string(),
444 expected_without_bounds.to_string()
445 );
446
447 let expected_with_bounds =
448 quote! { <'a, 'b : 'static, T, U : Clone, const N : usize, const M : u8> };
449 assert_eq!(
451 params.display_with_bounds().to_token_stream().to_string(),
452 expected_with_bounds.to_string()
453 );
454 }
455
456 #[test]
457 fn test_phantom_data_formatting() {
458 let empty = BoundedGenericParams { params: vec![] };
460 assert_eq!(
461 render_to_string(empty.display_as_phantom_data()),
462 ":: core :: marker :: PhantomData < (()) >"
463 );
464
465 let lifetime = BoundedGenericParams {
467 params: vec![BoundedGenericParam {
468 param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("a"))),
469 bounds: None,
470 }],
471 };
472 assert_eq!(
473 render_to_string(lifetime.display_as_phantom_data()),
474 "𝟋Ph < 'a >"
475 );
476
477 let type_param = BoundedGenericParams {
479 params: vec![BoundedGenericParam {
480 param: GenericParamName::Type(quote! { T }),
481 bounds: None,
482 }],
483 };
484 assert_eq!(
485 render_to_string(type_param.display_as_phantom_data()),
486 ":: core :: marker :: PhantomData < (T) >"
487 );
488
489 let const_param = BoundedGenericParams {
491 params: vec![BoundedGenericParam {
492 param: GenericParamName::Const(quote! { N }),
493 bounds: None, }],
495 };
496 assert_eq!(
497 render_to_string(const_param.display_as_phantom_data()),
498 ":: core :: marker :: PhantomData < ([u32 ; N]) >"
499 );
500
501 let mixed = BoundedGenericParams {
503 params: vec![
504 BoundedGenericParam {
505 param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("a"))),
506 bounds: None,
507 },
508 BoundedGenericParam {
509 param: GenericParamName::Type(quote! { T }),
510 bounds: Some(quote! { Clone }), },
512 BoundedGenericParam {
513 param: GenericParamName::Const(quote! { N }),
514 bounds: Some(quote! { usize }), },
516 ],
517 };
518 let actual_tokens = mixed.display_as_phantom_data();
519 let expected_tokens = quote! {
520 ::core::marker::PhantomData<(*mut &'a (), T, [u32; N])>
521 };
522 assert_eq!(
523 actual_tokens.to_token_stream().to_string(),
524 expected_tokens.to_string()
525 );
526 }
527}