Skip to main content

enum_info/
lib.rs

1// enum-info
2// 
3// Written by Calin Z. Baenen, 2026/07/19
4
5//! `enum-info` is a crate to generate an enum `impl` which can tell you the
6//!  number of – and, in most cases, list the – variants in an enum.
7//! 
8//! # Example
9//! ```rust
10//! use enum_info::enum_info;
11//! 
12//! #[enum_info]
13//! #[derive(PartialEq, Debug, Eq)]
14//! enum Characters {
15//! 	Katty,
16//! 	Jax
17//! }
18//! 
19//! assert_eq!(Characters::variant_count(), 2);
20//! assert_eq!(Characters::VARIANTS, [Characters::Katty, Characters::Jax]);
21//! ```
22
23use proc_macro::{
24	TokenStream,
25	Delimiter,
26	TokenTree,
27	Literal,
28	Spacing,
29	Group,
30	Ident,
31	Punct,
32	Span
33};
34
35use core::cmp::{PartialOrd, PartialEq, Ordering, Ord, Eq};
36use core::num::NonZeroUsize;
37use std::vec::Vec;
38
39
40
41
42
43#[allow(unused)]
44trait SubposData<S:Subpos> {
45	fn position(&self) -> &S;
46	
47	fn data(&self) -> &S::Data;
48}
49
50
51
52trait Subpos:PartialEq {
53	type Data:PartialEq;
54	
55	const ASSOCIATED_STAGE:LexicalPosStage;
56}
57
58impl Subpos for () {
59	type Data = ();
60	
61	const ASSOCIATED_STAGE:LexicalPosStage = LexicalPosStage::Start;
62}
63
64
65
66
67
68#[derive(PartialEq, Clone, Copy, Eq)]
69#[repr(u8)]
70enum GenericParamType {
71	Type     = 0,
72	Const    = 1,
73	Lifetime = 2
74}
75
76impl GenericParamType {
77	#[inline(always)] pub fn prefix(&self) -> Option<TokenTree> {
78		match self {
79			Self::Type => { None }
80			
81			Self::Const => { Some( TokenTree::Ident(Ident::new("const", Span::call_site())) ) }
82			
83			Self::Lifetime => { Some( TokenTree::Punct(Punct::new('\'', Spacing::Joint)) ) }
84		}
85	}
86}
87
88use GenericParamType::*;
89
90
91
92/// The position inside of a generic parameter.
93#[derive(PartialOrd, PartialEq, Clone, Copy, Ord, Eq)]
94enum GenericParamPos {
95	Name              = 0,
96	Bounds            = 1,
97	DefaultAssignment = 2
98}
99
100impl Subpos for GenericParamPos {
101	type Data = NonZeroUsize;
102	
103	const ASSOCIATED_STAGE:LexicalPosStage = LexicalPosStage::Generics;
104}
105
106
107
108#[derive(PartialOrd, PartialEq,Clone, Debug, Copy, Ord, Eq)]
109#[repr(usize)]
110enum LexicalPosStage {
111	/// Indicates anything before, and up to, the `enum` keyword.
112	Start = 0,
113	/// Indicates the position of the name of the enum.
114	Name = 1,
115	/// Indicates the position directly following the name but before the generics and enum body.
116	AfterName = 2,
117	/// Indicates the position between the outermost `<`/`>`.
118	/// 
119	/// If `depth` is greater than `1`, `pos` should not be [`GenericParamPos::Name`].
120	Generics = 3,
121	/// Indicates the position directly following the generics.
122	AfterGenerics = 4,
123	/// Indicates the position after the `where` keyword but before the enum body.
124	WhereClause = 5,
125	/// Indicates the position inside an enum body.
126	EnumBody = 6
127}
128
129
130
131#[derive(Clone, Copy)]
132union LexicalPosData {
133	enum_body:(EnumBodyPos, <EnumBodyPos as Subpos>::Data),
134	generics:(GenericParamPos, <GenericParamPos as Subpos>::Data),
135	usize:usize,
136	_marker:()
137}
138
139impl SubposData<EnumBodyPos> for LexicalPosData {
140	fn position(&self) -> &EnumBodyPos { unsafe { &self.enum_body.0 } }
141	
142	fn data(&self) -> &<EnumBodyPos as Subpos>::Data { unsafe { &self.enum_body.1 } }
143}
144
145impl SubposData<GenericParamPos> for LexicalPosData {
146	fn position(&self) -> &GenericParamPos { unsafe { &self.generics.0 } }
147	
148	fn data(&self) -> &<GenericParamPos as Subpos>::Data { unsafe { &self.generics.1 } }
149}
150
151
152
153#[derive(Clone)]
154struct GenericParam {
155	pub default_item:Option<TokenStream>,
156	pub param_type:GenericParamType,
157	pub bounds:Option<TokenStream>,
158	pub name:Option<Ident>
159}
160
161impl GenericParam {
162	pub const DEFAULT:Self = Self {default_item: None, param_type: GenericParamType::Type, bounds: None, name: None};
163}
164
165
166
167#[derive(PartialEq, Clone, Copy, Eq)]
168enum EnumBodyPos {
169	/// Indicates the position of an enum variant.
170	Variant,
171	/// Indicates the position after an enum variant's
172	///  name but before the `=`.
173	AfterVariant,
174	/// Indicates the position following the equals
175	///  sign in an enum variant declaration.
176	Definition
177}
178
179impl Subpos for EnumBodyPos {
180	type Data = ();
181	
182	const ASSOCIATED_STAGE:LexicalPosStage = LexicalPosStage::EnumBody;
183}
184
185
186
187/// The expected lexical position of a token.
188#[derive(Clone, Copy)]
189struct LexicalPos {
190	pub stage:LexicalPosStage,
191	pub data:LexicalPosData
192}
193
194impl LexicalPos {
195	#[inline(always)] pub const fn after_generics() -> Self {
196		Self {
197			stage: LexicalPosStage::AfterGenerics,
198			data: LexicalPosData { _marker: () }
199		}
200	}
201	
202	#[inline(always)] pub const fn where_clause() -> Self {
203		Self {
204			stage: LexicalPosStage::WhereClause,
205			data: LexicalPosData { usize: 0 }
206		}
207	}
208	
209	#[inline(always)] pub const fn after_name() -> Self {
210		Self {
211			stage: LexicalPosStage::AfterName,
212			data: LexicalPosData { _marker: () }
213		}
214	}
215	
216	#[inline(always)] pub const fn enum_body(pos:EnumBodyPos) -> Self {
217		Self {
218			stage: LexicalPosStage::EnumBody,
219			data: LexicalPosData { enum_body: (pos, ()) }
220		}
221	}
222	
223	#[inline(always)] pub const fn generics(depth:<GenericParamPos as Subpos>::Data, pos:GenericParamPos) -> Self {
224		Self {
225			stage: LexicalPosStage::Generics,
226			data: LexicalPosData { generics: (pos, depth) }
227		}
228	}
229	
230	#[inline(always)] pub const fn start() -> Self {
231		Self {
232			stage: LexicalPosStage::Start,
233			data: LexicalPosData { _marker: () }
234		}
235	}
236	
237	#[inline(always)] pub const fn name() -> Self {
238		Self {
239			stage: LexicalPosStage::Name,
240			data: LexicalPosData { _marker: () }
241		}
242	}
243	
244	/// Returns whether [`LexicalPosStage::EnumBody`] is a valid next position.
245	#[inline(always)] pub const fn enum_body_can_follow(&self) -> bool {
246		   self.stage as usize == LexicalPosStage::AfterName       as usize
247		|| self.stage as usize == LexicalPosStage::AfterGenerics   as usize
248		|| (
249		       self.stage as usize == LexicalPosStage::WhereClause as usize
250		    && unsafe { self.data.usize } == 0
251		)
252	}
253	
254	pub fn after_substate<S>(&self, substate:S) -> bool where LexicalPosData:SubposData<S>, S:Subpos+Ord {
255		   self.stage == S::ASSOCIATED_STAGE
256		&& <LexicalPosData as SubposData<S>>::position(&self.data).gt(&substate)
257	}
258	
259	/// Returns how deep into generics the lexical position is.
260	/// 
261	/// If the lexical position is not inside generics, `0` is returned.
262	#[inline(always)] pub const fn generics_depth(&self) -> usize {
263		if self.stage as usize != LexicalPosStage::Generics as usize { return 0; }
264		unsafe {
265			self.data.generics.1.get()
266		}
267	}
268	
269	pub fn in_substate<S>(&self, substate:S) -> bool where LexicalPosData:SubposData<S>, S:Subpos {
270		   self.stage == S::ASSOCIATED_STAGE
271		&& <LexicalPosData as SubposData<S>>::position(&self.data).eq(&substate)
272	}
273}
274
275impl PartialOrd<LexicalPosStage> for LexicalPos {
276	#[inline(always)] fn partial_cmp(&self, rhs:&LexicalPosStage) -> Option<Ordering> { Some(self.stage.cmp(rhs)) }
277}
278
279impl PartialOrd for LexicalPos {
280	/// Compares two [`LexicalPos`]es on the basis of which stage they are in.
281	#[inline(always)] fn partial_cmp(&self, rhs:&Self) -> Option<Ordering> { Some(self.cmp(rhs)) }
282}
283
284impl PartialEq<LexicalPosStage> for LexicalPos {
285	#[inline(always)] fn eq(&self, rhs:&LexicalPosStage) -> bool { self.stage.eq(rhs) }
286}
287
288impl PartialEq for LexicalPos {
289	#[inline] fn eq(&self, rhs:&Self) -> bool {
290		match (self.stage, rhs.stage) {
291			(LexicalPosStage::Generics, LexicalPosStage::Generics) => { unsafe {
292				self.data.generics == rhs.data.generics
293			} }
294			
295			(LexicalPosStage::EnumBody, LexicalPosStage::EnumBody) => { unsafe {
296				self.data.enum_body == rhs.data.enum_body
297			} }
298			
299			(x, y) => { x == y }
300		}
301	}
302}
303
304impl Ord for LexicalPos {
305	/// Compares two [`LexicalPos`]es on the basis of which stage they are in.
306	#[inline(always)] fn cmp(&self, rhs:&Self) -> Ordering { self.stage.cmp(&rhs.stage) }
307}
308
309impl Eq for LexicalPos {}
310
311
312
313
314
315/// Checks if two [`Option<Punct>`]s are equal based on their [`char`] and
316///  [`Spacing`] values.
317/// 
318/// If either value is [`Option::None`], then `false` is returned, regardless
319///  of whether both inputs are [`Option::None`].
320#[inline] fn cmp_punct(punct_a:Option<Punct>, punct_b:Option<Punct>) -> bool {
321	let (Some(a), Some(b)) = (punct_a, punct_b) else { return false; };
322	
323	   a.as_char() == b.as_char()
324	&& a.spacing() == b.spacing()
325}
326
327
328
329/// Generates an item `impl` with a guaranteed `variant_count` method and an
330///  associated constant, `VARIANTS`, for enums whose variants are all unit-like.
331/// 
332/// `variant_count` is a constant function which returns the number of variants
333///  the enum has.
334/// 
335/// `VARIANTS` is an array whose elements consist of each enum variant in order.
336#[proc_macro_attribute]
337pub fn enum_info(_attr:TokenStream, item:TokenStream) -> TokenStream {
338	let mut current_generic_param = GenericParam::DEFAULT;
339	let mut prev_tok_as_punct = None;
340	let mut generic_params = Vec::<GenericParam>::new();
341	let mut variant_names = Vec::<Ident>::new();
342	let mut where_clause = None;
343	let mut variant_ct = 0;
344	let mut all_unit = true;
345	let mut position = LexicalPos::start();
346	let mut output = item.clone();
347	let mut name = String::new();
348	
349	for token in item {
350		let mut generic_param_update = false;
351		let mut tok_as_punct = None;
352		let mut appendage = None;
353		
354		// Loop over the tokens of the outer item's content.
355		match token {
356			TokenTree::Group(g) if position.enum_body_can_follow() && !cmp_punct(prev_tok_as_punct.clone(), Some(Punct::new('!', Spacing::Alone))) && g.delimiter() == Delimiter::Brace => {
357				position = LexicalPos::enum_body(EnumBodyPos::Variant);
358				for subtoken in g.stream() {
359					match subtoken {
360						TokenTree::Group(_) if position.in_substate(EnumBodyPos::AfterVariant) => {
361							if !all_unit { continue; }
362							
363							variant_names.clear();
364							variant_names.shrink_to(0);
365							all_unit = false;
366						}
367						
368						TokenTree::Ident(i) if position.in_substate(EnumBodyPos::Variant) => {
369							position.data.enum_body.0 = EnumBodyPos::AfterVariant;
370							variant_ct += 1;
371							
372							if all_unit { variant_names.push(i); }
373						}
374						
375						TokenTree::Punct(p) => {
376							match p.as_char() {
377								'=' => {
378									position.data.enum_body.0 = EnumBodyPos::Definition;
379								}
380								
381								',' => {
382									position.data.enum_body.0 = EnumBodyPos::Variant;
383								}
384								
385								_ => {}
386							}
387						}
388						
389						_ => {}
390					}
391				}
392			}
393			
394			TokenTree::Group(g) if (position.after_substate(GenericParamPos::Name) || position == LexicalPosStage::WhereClause) => {
395				appendage = Some(TokenTree::Group(g));
396			}
397			
398			TokenTree::Ident(i) if position == LexicalPosStage::Name => {
399				name = i.to_string();
400				position = LexicalPos::after_name();
401				continue;
402			}
403			
404			TokenTree::Ident(i) => {
405				match i.to_string().as_str() {
406					"enum" if position == LexicalPosStage::Start => {
407						position = LexicalPos::name();
408					},
409					
410					// Const generics.
411					"const" if position.generics_depth() == 1
412					        && current_generic_param.name.is_none()
413					        && current_generic_param.param_type == Type => { current_generic_param.param_type = Const; },
414					
415					// Where clause.
416					"where" if position == LexicalPosStage::AfterName || position == LexicalPosStage::AfterGenerics => {
417						position = LexicalPos::where_clause();
418					}
419					
420					_ if position.in_substate(GenericParamPos::Name) && current_generic_param.name.is_none() => {
421						current_generic_param.name = Some(i);
422					},
423					
424					_ if (position.after_substate(GenericParamPos::Name) || position == LexicalPosStage::WhereClause) => {
425						appendage = Some(TokenTree::Ident(i));
426					}
427					
428					_ => {}
429				}
430			}
431			
432			TokenTree::Punct(p) => {
433				let c = p.as_char();
434				tok_as_punct = Some(p.clone());
435				
436				match c {
437					// Lifetimes.
438					'\'' if position.in_substate(GenericParamPos::Name)
439					     && current_generic_param.name.is_none()
440					     && current_generic_param.param_type == Type => { current_generic_param.param_type = Lifetime; }
441					
442					// Open generics.
443					'<' => {
444						if position == LexicalPosStage::AfterName {
445							position = LexicalPos::generics(unsafe { NonZeroUsize::new_unchecked(1) }, GenericParamPos::Name);
446							continue;
447						} else if position.after_substate(GenericParamPos::Name) {
448							appendage = Some(TokenTree::Punct(p));
449							position.data.generics.1 = unsafe { position.data.generics }.1.saturating_add(1);
450						} else if position == LexicalPosStage::WhereClause {
451							unsafe { position.data.usize += 1; }
452							appendage = Some(TokenTree::Punct(p));
453						}
454					}
455					
456					// Close generics.
457					'>' if position == LexicalPosStage::Generics && !cmp_punct(prev_tok_as_punct.clone(), Some(Punct::new('-', Spacing::Joint))) => 'close_generics: {
458						if unsafe { position.data.generics }.1.get() == 1 {
459							generic_param_update = true;
460							position = LexicalPos::after_generics();
461							
462							break 'close_generics;
463						}
464						
465						position.data.generics.1 =
466							unsafe { NonZeroUsize::new_unchecked(position.data.generics.1.get().saturating_sub(1)) };
467						appendage = Some(TokenTree::Punct(p));
468					}
469					
470					// Close generics (in `where` clauses).
471					'>' if position == LexicalPosStage::WhereClause && !cmp_punct(prev_tok_as_punct.clone(), Some(Punct::new('-', Spacing::Joint))) => 'close_generics: { unsafe {
472						appendage = Some(TokenTree::Punct(p));
473						
474						if position.data.usize < 1 { break 'close_generics; }
475						position.data.usize -= 1;
476					} }
477					
478					// Move from the name part of generics to the bounds.
479					':' if position.in_substate(GenericParamPos::Name) => {
480						position.data.generics.0 = GenericParamPos::Bounds;
481					}
482					
483					// Capture default values for generic parameters.
484					'=' if !position.after_substate(GenericParamPos::Bounds) && position == LexicalPosStage::Generics => {
485						position.data.generics.0 = GenericParamPos::DefaultAssignment;
486					}
487					
488					// Push the current generic parameter.
489					',' if position.generics_depth() == 1 => {
490						position.data.generics.0 = GenericParamPos::Name;
491						generic_param_update = true;
492					}
493					
494					_ if (position.after_substate(GenericParamPos::Name) || position == LexicalPosStage::WhereClause) => {
495						appendage = Some(TokenTree::Punct(p));
496					}
497					
498					_ => {}
499				}
500			}
501			
502			TokenTree::Literal(l) if (position.after_substate(GenericParamPos::Name) || position == LexicalPosStage::WhereClause) => {
503				appendage = Some(TokenTree::Literal(l));
504			}
505			
506			_ => {}
507		}
508		
509		'append_generics: {
510			if let Some(appendage) = appendage {
511				let append_to:&mut Option<TokenStream>;
512				if position.in_substate(GenericParamPos::Bounds) {
513					append_to = &mut current_generic_param.bounds;
514				} else if position.in_substate(GenericParamPos::DefaultAssignment) {
515					append_to = &mut current_generic_param.default_item;
516				} else if position == LexicalPosStage::WhereClause {
517					append_to = &mut where_clause;
518				} else {
519					break 'append_generics;
520				}
521				
522				if let &mut Some(ref mut append_to) = append_to {
523					append_to.extend([appendage].into_iter());
524				} else {
525					*append_to = Some(TokenStream::from_iter([appendage].into_iter()));
526				}
527			}
528		}
529		
530		'add_generic_param: {
531			if current_generic_param.name.is_none() { break 'add_generic_param; }
532			
533			if generic_param_update {
534				generic_params.push(current_generic_param);
535				current_generic_param = GenericParam::DEFAULT;
536			}
537		}
538		
539		prev_tok_as_punct = tok_as_punct;
540	}
541	
542	let mut r#impl = TokenStream::from_iter([
543		// #[inline(always)]
544		TokenTree::Punct(Punct::new('#', Spacing::Alone)),
545		TokenTree::Group(Group::new(
546			Delimiter::Bracket,
547			TokenStream::from_iter([
548				TokenTree::Ident(Ident::new("inline", Span::call_site())),
549				TokenTree::Group(Group::new(
550					Delimiter::Parenthesis,
551					TokenStream::from_iter([
552						TokenTree::Ident(Ident::new("always", Span::call_site()))
553					].into_iter())
554				))
555			].into_iter())
556		)),
557		
558		// /// ...
559		// pub const fn variant_count() -> ::core::primitive::usize
560		
561		TokenTree::Punct(Punct::new('#', Spacing::Alone)),
562		TokenTree::Group(Group::new(
563			Delimiter::Bracket,
564			TokenStream::from_iter([
565				TokenTree::Ident(Ident::new("doc", Span::call_site())),
566				TokenTree::Punct(Punct::new('=', Spacing::Alone)),
567				TokenTree::Literal(Literal::string("Returns the number of variants this enum has."))
568			].into_iter())
569		)),
570		TokenTree::Ident(Ident::new("pub", Span::call_site())),
571		TokenTree::Ident(Ident::new("const", Span::call_site())),
572		TokenTree::Ident(Ident::new("fn", Span::call_site())),
573		TokenTree::Ident(Ident::new("variant_count", Span::call_site())),
574		TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
575		TokenTree::Punct(Punct::new('-', Spacing::Joint)),
576		TokenTree::Punct(Punct::new('>', Spacing::Alone)),
577		TokenTree::Punct(Punct::new(':', Spacing::Joint)),
578		TokenTree::Punct(Punct::new(':', Spacing::Alone)),
579		TokenTree::Ident(Ident::new("core", Span::call_site())),
580		TokenTree::Punct(Punct::new(':', Spacing::Joint)),
581		TokenTree::Punct(Punct::new(':', Spacing::Alone)),
582		TokenTree::Ident(Ident::new("primitive", Span::call_site())),
583		TokenTree::Punct(Punct::new(':', Spacing::Joint)),
584		TokenTree::Punct(Punct::new(':', Spacing::Alone)),
585		TokenTree::Ident(Ident::new("usize", Span::call_site())),
586		
587		// { $variant_ct }
588		TokenTree::Group(Group::new(
589			Delimiter::Brace,
590			TokenStream::from_iter([
591				TokenTree::Literal(Literal::usize_unsuffixed(variant_ct))
592			].into_iter())
593		))
594	].into_iter());
595	
596	// pub const VARIANTS:[Self; $variant_ct] = [...$variant_names];
597	if all_unit {
598		let mut items = TokenStream::new();
599		
600		// [...$variant_names]
601		for variant in variant_names {
602			items.extend([
603				TokenTree::Ident(Ident::new("Self", Span::call_site())),
604				TokenTree::Punct(Punct::new(':', Spacing::Joint)),
605				TokenTree::Punct(Punct::new(':', Spacing::Alone)),
606				TokenTree::Ident(variant),
607				TokenTree::Punct(Punct::new(',', Spacing::Alone))
608			])
609		}
610		
611		r#impl.extend([
612			TokenTree::Ident(Ident::new("pub", Span::call_site())),
613			TokenTree::Ident(Ident::new("const", Span::call_site())),
614			TokenTree::Ident(Ident::new("VARIANTS", Span::call_site())),
615			TokenTree::Punct(Punct::new(':', Spacing::Alone)),
616			TokenTree::Group(Group::new(
617				Delimiter::Bracket,
618				TokenStream::from_iter([
619					TokenTree::Ident(Ident::new("Self", Span::call_site())),
620					TokenTree::Punct(Punct::new(';', Spacing::Alone)),
621					TokenTree::Literal(Literal::usize_unsuffixed(variant_ct))
622				].into_iter())
623			)),
624			TokenTree::Punct(Punct::new('=', Spacing::Alone)),
625			TokenTree::Group(Group::new(Delimiter::Bracket, items)),
626			TokenTree::Punct(Punct::new(';', Spacing::Alone))
627		]);
628	}
629	
630	let mut generics_names = TokenStream::new();
631	let mut generics_full = TokenStream::new();
632	
633	if generic_params.len() > 0 {
634		generics_names.extend([TokenTree::Punct(Punct::new('<', Spacing::Alone))]);
635		generics_full.extend([TokenTree::Punct(Punct::new('<', Spacing::Alone))]);
636		
637		for generic_item in generic_params {
638			if generic_item.param_type == Lifetime {
639				generics_names.extend([ unsafe { generic_item.param_type.prefix().unwrap_unchecked() } ]);
640			}
641			
642			generics_names.extend([
643				TokenTree::Ident(unsafe { generic_item.name.clone().unwrap_unchecked() }),
644				TokenTree::Punct(Punct::new(',', Spacing::Alone))
645			]);
646			
647			if let Some(prefix) = generic_item.param_type.prefix() {
648				generics_full.extend([prefix]);
649			}
650			
651			generics_full.extend([TokenTree::Ident(unsafe { generic_item.name.unwrap_unchecked() })]);
652			
653			if let Some(suffix) = generic_item.bounds {
654				generics_full.extend([
655					TokenTree::Punct(Punct::new(':', Spacing::Alone)),
656					TokenTree::Group(Group::new(Delimiter::None, suffix))
657				]);
658			}
659			
660			generics_full.extend([TokenTree::Punct(Punct::new(',', Spacing::Alone))]);
661		}
662		
663		generics_names.extend([TokenTree::Punct(Punct::new('>', Spacing::Alone))]);
664		generics_full.extend([TokenTree::Punct(Punct::new('>', Spacing::Alone))]);
665	}
666	
667	output.extend([
668		// impl<...$generic_parameters> $name<...$generic_parameter_names>
669		TokenTree::Ident(Ident::new("impl", Span::call_site())),
670		TokenTree::Group(Group::new(Delimiter::None, generics_full)),
671		TokenTree::Ident(Ident::new(&name, Span::call_site())),
672		TokenTree::Group(Group::new(Delimiter::None, generics_names)),
673		
674		// where $where_clause
675		TokenTree::Group(
676			Group::new(
677				Delimiter::None,
678				where_clause.map(
679					|ts| {
680						let mut w = TokenStream::from_iter([TokenTree::Ident(Ident::new("where", Span::call_site()))]);
681						w.extend(ts);
682						w
683					}
684				).unwrap_or_default()
685			)
686		),
687		
688		// { /* ... */ }
689		TokenTree::Group(Group::new(Delimiter::Brace, r#impl))
690	]);
691	output
692}