1use facet_macros_parse::{GenericParam, GenericParams, ToTokens, TokenStream};
2use quote::quote;
3
4use crate::LifetimeName;
5
6#[derive(Clone)]
8pub enum GenericParamName {
9 Lifetime(LifetimeName),
11
12 Type(TokenStream),
14
15 Const(TokenStream),
17}
18
19#[derive(Clone)]
21pub struct BoundedGenericParam {
22 pub param: GenericParamName,
24
25 pub bounds: Option<TokenStream>,
27}
28
29#[derive(Clone)]
34pub struct BoundedGenericParams {
35 pub params: Vec<BoundedGenericParam>,
37}
38
39pub struct WithBounds<'a>(&'a BoundedGenericParams);
48
49pub struct WithoutBounds<'a>(&'a BoundedGenericParams);
58
59pub struct AsPhantomData<'a>(&'a BoundedGenericParams);
69
70impl quote::ToTokens for AsPhantomData<'_> {
71 fn to_tokens(&self, tokens: &mut TokenStream) {
72 let mut temp = TokenStream::new();
73
74 {
75 #[expect(unused)]
76 let tokens = ();
77
78 let mut first_param = true;
80
81 for param in &self.0.params {
83 if !first_param {
84 temp.extend(quote! { , });
85 }
86
87 match ¶m.param {
88 GenericParamName::Lifetime(name) => {
89 temp.extend(quote! { *mut &#name () });
90 }
91 GenericParamName::Type(name) => {
92 temp.extend(quote! { #name });
93 }
94 GenericParamName::Const(name) => {
95 temp.extend(quote! { [u32; #name] });
96 }
97 }
98
99 first_param = false;
100 }
101
102 if first_param {
104 temp.extend(quote! { () });
105 }
106 }
107 tokens.extend(quote! {
108 ::core::marker::PhantomData<(#temp)>
109 })
110 }
111}
112
113impl BoundedGenericParams {
114 pub fn as_phantom_data(&self) -> AsPhantomData<'_> {
115 AsPhantomData(self)
116 }
117}
118
119impl quote::ToTokens for WithBounds<'_> {
120 fn to_tokens(&self, tokens: &mut TokenStream) {
121 if self.0.params.is_empty() {
122 return;
123 }
124
125 tokens.extend(quote! {
126 <
127 });
128
129 for (i, param) in self.0.params.iter().enumerate() {
130 if i > 0 {
131 tokens.extend(quote! { , });
132 }
133
134 match ¶m.param {
135 GenericParamName::Lifetime(name) => {
136 tokens.extend(quote! { #name });
137 }
138 GenericParamName::Type(name) => {
139 tokens.extend(quote! { #name });
140 }
141 GenericParamName::Const(name) => {
142 tokens.extend(quote! { const #name });
143 }
144 }
145
146 if let Some(bounds) = ¶m.bounds {
148 tokens.extend(quote! { : #bounds });
149 }
150 }
151
152 tokens.extend(quote! {
153 >
154 });
155 }
156}
157
158impl quote::ToTokens for WithoutBounds<'_> {
159 fn to_tokens(&self, tokens: &mut TokenStream) {
160 if self.0.params.is_empty() {
161 return;
162 }
163
164 tokens.extend(quote! {
165 <
166 });
167
168 for (i, param) in self.0.params.iter().enumerate() {
169 if i > 0 {
170 tokens.extend(quote! { , });
171 }
172
173 match ¶m.param {
174 GenericParamName::Lifetime(name) => {
175 tokens.extend(quote! { #name });
176 }
177 GenericParamName::Type(name) => {
178 tokens.extend(quote! { #name });
179 }
180 GenericParamName::Const(name) => {
181 tokens.extend(quote! { #name });
182 }
183 }
184 }
185
186 tokens.extend(quote! {
187 >
188 });
189 }
190}
191
192impl BoundedGenericParams {
193 pub fn display_with_bounds(&self) -> WithBounds<'_> {
194 WithBounds(self)
195 }
196
197 pub fn display_without_bounds(&self) -> WithoutBounds<'_> {
198 WithoutBounds(self)
199 }
200
201 pub fn display_as_phantom_data(&self) -> AsPhantomData<'_> {
212 AsPhantomData(self)
213 }
214
215 pub fn with(&self, param: BoundedGenericParam) -> Self {
216 let mut params = self.params.clone();
217
218 match ¶m.param {
219 GenericParamName::Lifetime(_) => {
220 let insert_position = params
222 .iter()
223 .position(|p| !matches!(p.param, GenericParamName::Lifetime(_)))
224 .unwrap_or(params.len());
225
226 params.insert(insert_position, param);
227 }
228 GenericParamName::Type(_) => {
229 let after_lifetimes = params
231 .iter()
232 .position(|p| !matches!(p.param, GenericParamName::Lifetime(_)))
233 .unwrap_or(params.len());
234
235 let insert_position = params[after_lifetimes..]
236 .iter()
237 .position(|p| matches!(p.param, GenericParamName::Const(_)))
238 .map(|pos| pos + after_lifetimes)
239 .unwrap_or(params.len());
240
241 params.insert(insert_position, param);
242 }
243 GenericParamName::Const(_) => {
244 params.push(param);
246 }
247 }
248
249 Self { params }
250 }
251
252 pub fn with_lifetime(&self, name: LifetimeName) -> Self {
257 self.with(BoundedGenericParam {
258 param: GenericParamName::Lifetime(name),
259 bounds: None,
260 })
261 }
262
263 pub fn with_type(&self, name: TokenStream) -> Self {
268 self.with(BoundedGenericParam {
269 param: GenericParamName::Type(name),
270 bounds: None,
271 })
272 }
273}
274
275impl BoundedGenericParams {
276 pub fn parse(generics: Option<&GenericParams>) -> Self {
286 let Some(generics) = generics else {
287 return Self { params: Vec::new() };
288 };
289
290 let mut params = Vec::new();
291
292 for param in generics.params.iter() {
293 match ¶m.value {
294 GenericParam::Type {
295 name,
296 bounds,
297 default: _,
298 } => {
299 params.push(BoundedGenericParam {
300 param: GenericParamName::Type(name.to_token_stream()),
301 bounds: bounds
302 .as_ref()
303 .map(|bounds| bounds.second.to_token_stream()),
304 });
305 }
306 GenericParam::Lifetime { name, bounds } => {
307 params.push(BoundedGenericParam {
308 param: GenericParamName::Lifetime(LifetimeName(name.name.clone())),
309 bounds: bounds
310 .as_ref()
311 .map(|bounds| bounds.second.to_token_stream()),
312 });
313 }
314 GenericParam::Const {
315 _const: _,
316 name,
317 _colon: _,
318 typ,
319 default: _,
320 } => {
321 params.push(BoundedGenericParam {
322 param: GenericParamName::Const(name.to_token_stream()),
323 bounds: Some(typ.to_token_stream()),
324 });
325 }
326 }
327 }
328
329 Self { params }
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::{BoundedGenericParam, BoundedGenericParams, GenericParamName};
336 use crate::LifetimeName;
337 use quote::{ToTokens as _, quote};
338
339 fn render_to_string<T: quote::ToTokens>(t: T) -> String {
341 quote!(#t).to_string()
342 }
343
344 #[test]
345 fn test_empty_generic_params() {
346 let p = BoundedGenericParams { params: vec![] };
347 assert_eq!(render_to_string(p.display_with_bounds()), "");
348 assert_eq!(render_to_string(p.display_without_bounds()), "");
349 }
350
351 #[test]
352 fn print_multiple_generic_params() {
353 let p = BoundedGenericParams {
354 params: vec![
355 BoundedGenericParam {
356 bounds: Some(quote! { 'static }),
357 param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("a"))),
358 },
359 BoundedGenericParam {
360 bounds: Some(quote! { Clone + Debug }),
361 param: GenericParamName::Type(quote! { T }),
362 },
363 BoundedGenericParam {
364 bounds: None,
365 param: GenericParamName::Type(quote! { U }),
366 },
367 BoundedGenericParam {
368 bounds: Some(quote! { usize }), param: GenericParamName::Const(quote! { N }),
370 },
371 ],
372 };
373 let expected_with_bounds = quote! { <'a : 'static, T : Clone + Debug, U, const N : usize> };
375 assert_eq!(
376 p.display_with_bounds().to_token_stream().to_string(),
377 expected_with_bounds.to_string()
378 );
379
380 let expected_without_bounds = quote! { <'a, T, U, N> }; assert_eq!(
383 p.display_without_bounds().to_token_stream().to_string(),
384 expected_without_bounds.to_string()
385 );
386 }
387
388 #[test]
389 fn test_add_mixed_parameters() {
390 let mut params = BoundedGenericParams { params: vec![] };
392
393 params = params.with(BoundedGenericParam {
395 bounds: None,
396 param: GenericParamName::Type(quote! { T }),
397 });
398
399 params = params.with(BoundedGenericParam {
400 bounds: Some(quote! { usize }), param: GenericParamName::Const(quote! { N }),
402 });
403
404 params = params.with(BoundedGenericParam {
405 bounds: None,
406 param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("a"))),
407 });
408
409 params = params.with(BoundedGenericParam {
410 bounds: Some(quote! { Clone }),
411 param: GenericParamName::Type(quote! { U }),
412 });
413
414 params = params.with(BoundedGenericParam {
415 bounds: Some(quote! { 'static }),
416 param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("b"))),
417 });
418
419 params = params.with(BoundedGenericParam {
420 bounds: Some(quote! { u8 }), param: GenericParamName::Const(quote! { M }),
422 });
423
424 let expected_without_bounds = quote! { <'a, 'b, T, U, N, M> };
426 assert_eq!(
428 params
429 .display_without_bounds()
430 .to_token_stream()
431 .to_string(),
432 expected_without_bounds.to_string()
433 );
434
435 let expected_with_bounds =
436 quote! { <'a, 'b : 'static, T, U : Clone, const N : usize, const M : u8> };
437 assert_eq!(
439 params.display_with_bounds().to_token_stream().to_string(),
440 expected_with_bounds.to_string()
441 );
442 }
443
444 #[test]
445 fn test_phantom_data_formatting() {
446 let empty = BoundedGenericParams { params: vec![] };
448 assert_eq!(
449 render_to_string(empty.display_as_phantom_data()),
450 ":: core :: marker :: PhantomData < (()) >"
451 );
452
453 let lifetime = BoundedGenericParams {
455 params: vec![BoundedGenericParam {
456 param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("a"))),
457 bounds: None,
458 }],
459 };
460 assert_eq!(
461 render_to_string(lifetime.display_as_phantom_data()),
462 ":: core :: marker :: PhantomData < (* mut & 'a ()) >"
463 );
464
465 let type_param = BoundedGenericParams {
467 params: vec![BoundedGenericParam {
468 param: GenericParamName::Type(quote! { T }),
469 bounds: None,
470 }],
471 };
472 assert_eq!(
473 render_to_string(type_param.display_as_phantom_data()),
474 ":: core :: marker :: PhantomData < (T) >"
475 );
476
477 let const_param = BoundedGenericParams {
479 params: vec![BoundedGenericParam {
480 param: GenericParamName::Const(quote! { N }),
481 bounds: None, }],
483 };
484 assert_eq!(
485 render_to_string(const_param.display_as_phantom_data()),
486 ":: core :: marker :: PhantomData < ([u32 ; N]) >"
487 );
488
489 let mixed = BoundedGenericParams {
491 params: vec![
492 BoundedGenericParam {
493 param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("a"))),
494 bounds: None,
495 },
496 BoundedGenericParam {
497 param: GenericParamName::Type(quote! { T }),
498 bounds: Some(quote! { Clone }), },
500 BoundedGenericParam {
501 param: GenericParamName::Const(quote! { N }),
502 bounds: Some(quote! { usize }), },
504 ],
505 };
506 let actual_tokens = mixed.display_as_phantom_data();
507 let expected_tokens = quote! {
508 ::core::marker::PhantomData<(*mut &'a (), T, [u32; N])>
509 };
510 assert_eq!(
511 actual_tokens.to_token_stream().to_string(),
512 expected_tokens.to_string()
513 );
514 }
515}