1use darling::{FromDeriveInput, FromField, FromVariant};
4use proc_macro::TokenStream;
5use proc_macro2::Ident;
6use quote::{format_ident, quote};
7use std::collections::HashMap;
8use syn::{parse_macro_input, DeriveInput};
9
10#[derive(Debug, FromDeriveInput)]
12#[darling(attributes(action), supports(enum_any))]
13struct ActionOpts {
14 ident: syn::Ident,
15 data: darling::ast::Data<ActionVariant, ()>,
16
17 #[darling(default)]
19 infer_categories: bool,
20
21 #[darling(default)]
23 generate_dispatcher: bool,
24}
25
26#[derive(Debug, FromVariant)]
28#[darling(attributes(action))]
29struct ActionVariant {
30 ident: syn::Ident,
31 fields: darling::ast::Fields<()>,
32
33 #[darling(default)]
35 category: Option<String>,
36
37 #[darling(default)]
39 skip_category: bool,
40}
41
42const ACTION_VERBS: &[&str] = &[
46 "Start", "End", "Open", "Close", "Submit", "Confirm", "Cancel", "Next", "Prev", "Up", "Down", "Left", "Right", "Enter", "Exit", "Escape",
49 "Add", "Remove", "Clear", "Update", "Set", "Get", "Load", "Save", "Delete", "Create",
51 "Fetch", "Change", "Resize", "Error", "Show", "Hide", "Enable", "Disable", "Toggle", "Focus", "Blur", "Select", "Move", "Copy", "Cycle", "Reset", "Scroll",
57];
58
59fn split_pascal_case(s: &str) -> Vec<String> {
61 let mut parts = Vec::new();
62 let mut current = String::new();
63
64 for ch in s.chars() {
65 if ch.is_uppercase() && !current.is_empty() {
66 parts.push(current);
67 current = String::new();
68 }
69 current.push(ch);
70 }
71 if !current.is_empty() {
72 parts.push(current);
73 }
74 parts
75}
76
77fn to_snake_case(s: &str) -> String {
79 let mut result = String::new();
80 for (i, ch) in s.chars().enumerate() {
81 if ch.is_uppercase() {
82 if i > 0 {
83 result.push('_');
84 }
85 result.push(ch.to_lowercase().next().unwrap());
86 } else {
87 result.push(ch);
88 }
89 }
90 result
91}
92
93fn to_pascal_case(s: &str) -> String {
95 s.split('_')
96 .map(|part| {
97 let mut chars = part.chars();
98 match chars.next() {
99 None => String::new(),
100 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
101 }
102 })
103 .collect()
104}
105
106fn infer_category(name: &str) -> Option<String> {
108 let parts = split_pascal_case(name);
109 if parts.is_empty() {
110 return None;
111 }
112
113 if parts[0] == "Did" {
115 return Some("async_result".to_string());
116 }
117
118 if parts.len() < 2 {
120 return None;
121 }
122
123 let first_is_verb = ACTION_VERBS.contains(&parts[0].as_str());
129
130 let mut prefix_end = parts.len();
131 let mut found_verb = false;
132 for (i, part) in parts.iter().enumerate().skip(1) {
133 if part == "Did" || ACTION_VERBS.contains(&part.as_str()) {
136 prefix_end = i;
137 found_verb = true;
138 break;
139 }
140 }
141
142 if first_is_verb {
146 return None;
147 }
148
149 if !found_verb {
151 return None;
152 }
153
154 if prefix_end == 0 {
155 return None;
156 }
157
158 let prefix_parts: Vec<&str> = parts[..prefix_end].iter().map(|s| s.as_str()).collect();
159 let prefix = prefix_parts.join("");
160
161 Some(to_snake_case(&prefix))
162}
163
164#[proc_macro_derive(Action, attributes(action))]
196pub fn derive_action(input: TokenStream) -> TokenStream {
197 let input = parse_macro_input!(input as DeriveInput);
198
199 let opts = match ActionOpts::from_derive_input(&input) {
201 Ok(opts) => opts,
202 Err(e) => return e.write_errors().into(),
203 };
204
205 let name = &opts.ident;
206
207 let variants = match &opts.data {
208 darling::ast::Data::Enum(variants) => variants,
209 _ => {
210 return syn::Error::new_spanned(&input, "Action can only be derived for enums")
211 .to_compile_error()
212 .into();
213 }
214 };
215
216 let syn_variants = match &input.data {
218 syn::Data::Enum(data) => &data.variants,
219 _ => unreachable!(), };
221
222 let name_arms = variants.iter().map(|v| {
224 let variant_name = &v.ident;
225 let variant_str = variant_name.to_string();
226
227 match &v.fields.style {
228 darling::ast::Style::Unit => quote! {
229 #name::#variant_name => #variant_str
230 },
231 darling::ast::Style::Tuple => quote! {
232 #name::#variant_name(..) => #variant_str
233 },
234 darling::ast::Style::Struct => quote! {
235 #name::#variant_name { .. } => #variant_str
236 },
237 }
238 });
239
240 let params_arms = syn_variants.iter().map(|v| {
242 let variant_name = &v.ident;
243
244 match &v.fields {
245 syn::Fields::Unit => quote! {
246 #name::#variant_name => ::std::string::String::new()
247 },
248 syn::Fields::Unnamed(fields) => {
249 let field_count = fields.unnamed.len();
250 let field_names: Vec<_> =
251 (0..field_count).map(|i| format_ident!("_{}", i)).collect();
252 if field_count == 1 {
253 quote! {
254 #name::#variant_name(#(#field_names),*) => {
255 tui_dispatch::debug::debug_string(&#(#field_names),*)
256 }
257 }
258 } else {
259 let parts = field_names.iter().map(|field| {
260 quote! { tui_dispatch::debug::debug_string(&#field) }
261 });
262 quote! {
263 #name::#variant_name(#(#field_names),*) => {
264 let values = ::std::vec![#(#parts),*];
265 format!("({})", values.join(", "))
266 }
267 }
268 }
269 }
270 syn::Fields::Named(fields) => {
271 let field_names: Vec<_> = fields
272 .named
273 .iter()
274 .filter_map(|f| f.ident.as_ref())
275 .collect();
276 if field_names.is_empty() {
277 quote! {
278 #name::#variant_name { .. } => ::std::string::String::new()
279 }
280 } else {
281 let parts = field_names.iter().map(|field| {
282 let label = field.to_string();
283 quote! {
284 format!("{}: {}", #label, tui_dispatch::debug::debug_string(&#field))
285 }
286 });
287 quote! {
288 #name::#variant_name { #(#field_names),*, .. } => {
289 let values = ::std::vec![#(#parts),*];
290 format!("{{{}}}", values.join(", "))
291 }
292 }
293 }
294 }
295 }
296 });
297
298 let params_pretty_arms = syn_variants.iter().map(|v| {
299 let variant_name = &v.ident;
300
301 match &v.fields {
302 syn::Fields::Unit => quote! {
303 #name::#variant_name => ::std::string::String::new()
304 },
305 syn::Fields::Unnamed(fields) => {
306 let field_count = fields.unnamed.len();
307 let field_names: Vec<_> =
308 (0..field_count).map(|i| format_ident!("_{}", i)).collect();
309 if field_count == 1 {
310 quote! {
311 #name::#variant_name(#(#field_names),*) => {
312 tui_dispatch::debug::debug_string_pretty(&#(#field_names),*)
313 }
314 }
315 } else {
316 let parts = field_names.iter().map(|field| {
317 quote! { tui_dispatch::debug::debug_string_pretty(&#field) }
318 });
319 quote! {
320 #name::#variant_name(#(#field_names),*) => {
321 let values = ::std::vec![#(#parts),*];
322 format!("({})", values.join(", "))
323 }
324 }
325 }
326 }
327 syn::Fields::Named(fields) => {
328 let field_names: Vec<_> = fields
329 .named
330 .iter()
331 .filter_map(|f| f.ident.as_ref())
332 .collect();
333 if field_names.is_empty() {
334 quote! {
335 #name::#variant_name { .. } => ::std::string::String::new()
336 }
337 } else {
338 let parts = field_names.iter().map(|field| {
339 let label = field.to_string();
340 quote! {
341 format!("{}: {}", #label, tui_dispatch::debug::debug_string_pretty(&#field))
342 }
343 });
344 quote! {
345 #name::#variant_name { #(#field_names),*, .. } => {
346 let values = ::std::vec![#(#parts),*];
347 format!("{{{}}}", values.join(", "))
348 }
349 }
350 }
351 }
352 }
353 });
354
355 let mut expanded = quote! {
356 impl tui_dispatch::Action for #name {
357 fn name(&self) -> &'static str {
358 match self {
359 #(#name_arms),*
360 }
361 }
362 }
363
364 impl tui_dispatch::ActionParams for #name {
365 fn params(&self) -> ::std::string::String {
366 match self {
367 #(#params_arms),*
368 }
369 }
370
371 fn params_pretty(&self) -> ::std::string::String {
372 match self {
373 #(#params_pretty_arms),*
374 }
375 }
376 }
377 };
378
379 if opts.infer_categories {
381 let mut categories: HashMap<String, Vec<&Ident>> = HashMap::new();
383 let mut variant_categories: Vec<(&Ident, Option<String>)> = Vec::new();
384
385 for v in variants.iter() {
386 let cat = if v.skip_category {
387 None
388 } else if let Some(ref explicit_cat) = v.category {
389 Some(explicit_cat.clone())
390 } else {
391 infer_category(&v.ident.to_string())
392 };
393
394 variant_categories.push((&v.ident, cat.clone()));
395
396 if let Some(ref category) = cat {
397 categories
398 .entry(category.clone())
399 .or_default()
400 .push(&v.ident);
401 }
402 }
403
404 let mut sorted_categories: Vec<_> = categories.keys().cloned().collect();
406 sorted_categories.sort();
407
408 let category_arms_dedup: Vec<_> = variant_categories
410 .iter()
411 .map(|(variant, cat)| {
412 let cat_expr = match cat {
413 Some(c) => quote! { ::core::option::Option::Some(#c) },
414 None => quote! { ::core::option::Option::None },
415 };
416 quote! { #name::#variant { .. } => #cat_expr }
418 })
419 .collect();
420
421 let category_enum_name = format_ident!("{}Category", name);
423 let category_variants: Vec<_> = sorted_categories
424 .iter()
425 .map(|c| format_ident!("{}", to_pascal_case(c)))
426 .collect();
427 let category_variant_names: Vec<_> = sorted_categories.clone();
428
429 let category_enum_arms: Vec<_> = variant_categories
431 .iter()
432 .map(|(variant, cat)| {
433 let cat_variant = match cat {
434 Some(c) => format_ident!("{}", to_pascal_case(c)),
435 None => format_ident!("Uncategorized"),
436 };
437 quote! { #name::#variant { .. } => #category_enum_name::#cat_variant }
438 })
439 .collect();
440
441 let predicates: Vec<_> = sorted_categories
443 .iter()
444 .map(|cat| {
445 let predicate_name = format_ident!("is_{}", cat);
446 let cat_variants = categories.get(cat).unwrap();
447 let patterns: Vec<_> = cat_variants
448 .iter()
449 .map(|v| quote! { #name::#v { .. } })
450 .collect();
451 let doc = format!(
452 "Returns true if this action belongs to the `{}` category.",
453 cat
454 );
455
456 quote! {
457 #[doc = #doc]
458 pub fn #predicate_name(&self) -> bool {
459 matches!(self, #(#patterns)|*)
460 }
461 }
462 })
463 .collect();
464
465 let category_enum_doc = format!(
467 "Action categories for [`{}`].\n\n\
468 Use [`{}::category_enum()`] to get the category of an action.",
469 name, name
470 );
471
472 expanded = quote! {
473 #expanded
474
475 #[doc = #category_enum_doc]
476 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
477 pub enum #category_enum_name {
478 #(#category_variants,)*
479 Uncategorized,
481 }
482
483 impl #category_enum_name {
484 pub fn all() -> &'static [Self] {
486 &[#(Self::#category_variants,)* Self::Uncategorized]
487 }
488
489 pub fn name(&self) -> &'static str {
491 match self {
492 #(Self::#category_variants => #category_variant_names,)*
493 Self::Uncategorized => "uncategorized",
494 }
495 }
496 }
497
498 impl #name {
499 pub fn category(&self) -> ::core::option::Option<&'static str> {
501 match self {
502 #(#category_arms_dedup,)*
503 }
504 }
505
506 pub fn category_enum(&self) -> #category_enum_name {
508 match self {
509 #(#category_enum_arms,)*
510 }
511 }
512
513 #(#predicates)*
514 }
515
516 impl tui_dispatch::ActionCategory for #name {
517 type Category = #category_enum_name;
518
519 fn category(&self) -> ::core::option::Option<&'static str> {
520 #name::category(self)
521 }
522
523 fn category_enum(&self) -> Self::Category {
524 #name::category_enum(self)
525 }
526 }
527 };
528
529 if opts.generate_dispatcher {
531 let dispatcher_trait_name = format_ident!("{}Dispatcher", name);
532
533 let dispatch_methods: Vec<_> = sorted_categories
534 .iter()
535 .map(|cat| {
536 let method_name = format_ident!("dispatch_{}", cat);
537 let doc = format!("Handle actions in the `{}` category.", cat);
538 quote! {
539 #[doc = #doc]
540 fn #method_name(&mut self, action: &#name) -> bool {
541 false
542 }
543 }
544 })
545 .collect();
546
547 let dispatch_arms: Vec<_> = sorted_categories
548 .iter()
549 .map(|cat| {
550 let method_name = format_ident!("dispatch_{}", cat);
551 let cat_variant = format_ident!("{}", to_pascal_case(cat));
552 quote! {
553 #category_enum_name::#cat_variant => self.#method_name(action)
554 }
555 })
556 .collect();
557
558 let dispatcher_doc = format!(
559 "Dispatcher trait for [`{}`].\n\n\
560 Implement the `dispatch_*` methods for each category you want to handle.\n\
561 The [`dispatch()`](Self::dispatch) method automatically routes to the correct handler.",
562 name
563 );
564
565 expanded = quote! {
566 #expanded
567
568 #[doc = #dispatcher_doc]
569 pub trait #dispatcher_trait_name {
570 #(#dispatch_methods)*
571
572 fn dispatch_uncategorized(&mut self, action: &#name) -> bool {
574 false
575 }
576
577 fn dispatch(&mut self, action: &#name) -> bool {
579 match action.category_enum() {
580 #(#dispatch_arms,)*
581 #category_enum_name::Uncategorized => self.dispatch_uncategorized(action),
582 }
583 }
584 }
585 };
586 }
587 }
588
589 TokenStream::from(expanded)
590}
591
592#[proc_macro_derive(BindingContext)]
611pub fn derive_binding_context(input: TokenStream) -> TokenStream {
612 let input = parse_macro_input!(input as DeriveInput);
613 let name = &input.ident;
614
615 let expanded = match &input.data {
616 syn::Data::Enum(data) => {
617 for variant in &data.variants {
619 if !matches!(variant.fields, syn::Fields::Unit) {
620 return syn::Error::new_spanned(
621 variant,
622 "BindingContext can only be derived for enums with unit variants",
623 )
624 .to_compile_error()
625 .into();
626 }
627 }
628
629 let variant_names: Vec<_> = data.variants.iter().map(|v| &v.ident).collect();
630 let variant_strings: Vec<_> = variant_names
631 .iter()
632 .map(|v| to_snake_case(&v.to_string()))
633 .collect();
634
635 let name_arms = variant_names
636 .iter()
637 .zip(variant_strings.iter())
638 .map(|(v, s)| {
639 quote! { #name::#v => #s }
640 });
641
642 let from_name_arms = variant_names
643 .iter()
644 .zip(variant_strings.iter())
645 .map(|(v, s)| {
646 quote! { #s => ::core::option::Option::Some(#name::#v) }
647 });
648
649 let all_variants = variant_names.iter().map(|v| quote! { #name::#v });
650
651 quote! {
652 impl tui_dispatch::BindingContext for #name {
653 fn name(&self) -> &'static str {
654 match self {
655 #(#name_arms),*
656 }
657 }
658
659 fn from_name(name: &str) -> ::core::option::Option<Self> {
660 match name {
661 #(#from_name_arms,)*
662 _ => ::core::option::Option::None,
663 }
664 }
665
666 fn all() -> &'static [Self] {
667 static ALL: &[#name] = &[#(#all_variants),*];
668 ALL
669 }
670 }
671 }
672 }
673 _ => {
674 return syn::Error::new_spanned(input, "BindingContext can only be derived for enums")
675 .to_compile_error()
676 .into();
677 }
678 };
679
680 TokenStream::from(expanded)
681}
682
683#[proc_macro_derive(ComponentId)]
699pub fn derive_component_id(input: TokenStream) -> TokenStream {
700 let input = parse_macro_input!(input as DeriveInput);
701 let name = &input.ident;
702
703 let expanded = match &input.data {
704 syn::Data::Enum(data) => {
705 for variant in &data.variants {
707 if !matches!(variant.fields, syn::Fields::Unit) {
708 return syn::Error::new_spanned(
709 variant,
710 "ComponentId can only be derived for enums with unit variants",
711 )
712 .to_compile_error()
713 .into();
714 }
715 }
716
717 let variant_names: Vec<_> = data.variants.iter().map(|v| &v.ident).collect();
718 let variant_strings: Vec<_> = variant_names.iter().map(|v| v.to_string()).collect();
719
720 let name_arms = variant_names
721 .iter()
722 .zip(variant_strings.iter())
723 .map(|(v, s)| {
724 quote! { #name::#v => #s }
725 });
726
727 quote! {
728 impl tui_dispatch::ComponentId for #name {
729 fn name(&self) -> &'static str {
730 match self {
731 #(#name_arms),*
732 }
733 }
734 }
735 }
736 }
737 _ => {
738 return syn::Error::new_spanned(input, "ComponentId can only be derived for enums")
739 .to_compile_error()
740 .into();
741 }
742 };
743
744 TokenStream::from(expanded)
745}
746
747#[derive(Debug, FromDeriveInput)]
753#[darling(attributes(debug_state), supports(struct_named))]
754struct DebugStateOpts {
755 ident: syn::Ident,
756 data: darling::ast::Data<(), DebugStateField>,
757}
758
759#[derive(Debug, FromField)]
761#[darling(attributes(debug))]
762struct DebugStateField {
763 ident: Option<syn::Ident>,
764
765 #[darling(default)]
767 section: Option<String>,
768
769 #[darling(default)]
771 skip: bool,
772
773 #[darling(default)]
775 format: Option<String>,
776
777 #[darling(default)]
779 label: Option<String>,
780
781 #[darling(default)]
783 debug_fmt: bool,
784}
785
786#[proc_macro_derive(DebugState, attributes(debug, debug_state))]
824pub fn derive_debug_state(input: TokenStream) -> TokenStream {
825 let input = parse_macro_input!(input as DeriveInput);
826
827 let opts = match DebugStateOpts::from_derive_input(&input) {
828 Ok(opts) => opts,
829 Err(e) => return e.write_errors().into(),
830 };
831
832 let name = &opts.ident;
833 let default_section = name.to_string();
834
835 let fields = match &opts.data {
836 darling::ast::Data::Struct(fields) => fields,
837 _ => {
838 return syn::Error::new_spanned(&input, "DebugState can only be derived for structs")
839 .to_compile_error()
840 .into();
841 }
842 };
843
844 let mut sections: HashMap<String, Vec<&DebugStateField>> = HashMap::new();
846 let mut section_order: Vec<String> = Vec::new();
847
848 for field in fields.iter() {
849 if field.skip {
850 continue;
851 }
852
853 let section_name = field
854 .section
855 .clone()
856 .unwrap_or_else(|| default_section.clone());
857
858 if !section_order.contains(§ion_name) {
859 section_order.push(section_name.clone());
860 }
861
862 sections.entry(section_name).or_default().push(field);
863 }
864
865 let section_code: Vec<_> = section_order
867 .iter()
868 .map(|section_name| {
869 let fields_in_section = sections.get(section_name).unwrap();
870
871 let entry_calls: Vec<_> = fields_in_section
872 .iter()
873 .filter_map(|field| {
874 let field_ident = field.ident.as_ref()?;
875 let label = field
876 .label
877 .clone()
878 .unwrap_or_else(|| field_ident.to_string());
879
880 let value_expr = if let Some(ref fmt) = field.format {
881 quote! { format!(#fmt, self.#field_ident) }
882 } else if field.debug_fmt {
883 quote! { format!("{:?}", self.#field_ident) }
884 } else {
885 quote! { tui_dispatch::debug::debug_string(&self.#field_ident) }
886 };
887
888 Some(quote! {
889 .entry(#label, #value_expr)
890 })
891 })
892 .collect();
893
894 quote! {
895 tui_dispatch::debug::DebugSection::new(#section_name)
896 #(#entry_calls)*
897 }
898 })
899 .collect();
900
901 let expanded = quote! {
902 impl tui_dispatch::debug::DebugState for #name {
903 fn debug_sections(&self) -> ::std::vec::Vec<tui_dispatch::debug::DebugSection> {
904 ::std::vec![
905 #(#section_code),*
906 ]
907 }
908 }
909 };
910
911 TokenStream::from(expanded)
912}
913
914#[derive(Debug, FromField)]
920#[darling(attributes(flag))]
921struct FeatureFlagsField {
922 ident: Option<syn::Ident>,
923 ty: syn::Type,
924
925 #[darling(default)]
927 default: Option<bool>,
928}
929
930#[derive(Debug, FromDeriveInput)]
932#[darling(attributes(feature_flags), supports(struct_named))]
933struct FeatureFlagsOpts {
934 ident: syn::Ident,
935 data: darling::ast::Data<(), FeatureFlagsField>,
936}
937
938#[proc_macro_derive(FeatureFlags, attributes(flag, feature_flags))]
969pub fn derive_feature_flags(input: TokenStream) -> TokenStream {
970 let input = parse_macro_input!(input as DeriveInput);
971
972 let opts = match FeatureFlagsOpts::from_derive_input(&input) {
973 Ok(opts) => opts,
974 Err(e) => return e.write_errors().into(),
975 };
976
977 let name = &opts.ident;
978
979 let fields = match &opts.data {
980 darling::ast::Data::Struct(fields) => fields,
981 _ => {
982 return syn::Error::new_spanned(
983 &input,
984 "FeatureFlags can only be derived for structs with named fields",
985 )
986 .to_compile_error()
987 .into();
988 }
989 };
990
991 let bool_fields: Vec<_> = fields
993 .iter()
994 .filter_map(|f| {
995 let ident = f.ident.as_ref()?;
996 if let syn::Type::Path(type_path) = &f.ty {
998 if type_path.path.is_ident("bool") {
999 return Some((ident.clone(), f.default.unwrap_or(false)));
1000 }
1001 }
1002 None
1003 })
1004 .collect();
1005
1006 if bool_fields.is_empty() {
1007 return syn::Error::new_spanned(
1008 &input,
1009 "FeatureFlags struct must have at least one bool field",
1010 )
1011 .to_compile_error()
1012 .into();
1013 }
1014
1015 let is_enabled_arms: Vec<_> = bool_fields
1017 .iter()
1018 .map(|(ident, _)| {
1019 let name_str = ident.to_string();
1020 quote! { #name_str => ::core::option::Option::Some(self.#ident) }
1021 })
1022 .collect();
1023
1024 let set_arms: Vec<_> = bool_fields
1026 .iter()
1027 .map(|(ident, _)| {
1028 let name_str = ident.to_string();
1029 quote! {
1030 #name_str => {
1031 self.#ident = enabled;
1032 true
1033 }
1034 }
1035 })
1036 .collect();
1037
1038 let flag_names: Vec<_> = bool_fields
1040 .iter()
1041 .map(|(ident, _)| ident.to_string())
1042 .collect();
1043
1044 let default_fields: Vec<_> = bool_fields
1046 .iter()
1047 .map(|(ident, default)| {
1048 quote! { #ident: #default }
1049 })
1050 .collect();
1051
1052 let expanded = quote! {
1053 impl tui_dispatch::FeatureFlags for #name {
1054 fn is_enabled(&self, name: &str) -> ::core::option::Option<bool> {
1055 match name {
1056 #(#is_enabled_arms,)*
1057 _ => ::core::option::Option::None,
1058 }
1059 }
1060
1061 fn set(&mut self, name: &str, enabled: bool) -> bool {
1062 match name {
1063 #(#set_arms)*
1064 _ => false,
1065 }
1066 }
1067
1068 fn all_flags() -> &'static [&'static str] {
1069 &[#(#flag_names),*]
1070 }
1071 }
1072
1073 impl ::core::default::Default for #name {
1074 fn default() -> Self {
1075 Self {
1076 #(#default_fields,)*
1077 }
1078 }
1079 }
1080 };
1081
1082 TokenStream::from(expanded)
1083}