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