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 ACTION_VERBS.contains(&part.as_str()) {
134 prefix_end = i;
135 found_verb = true;
136 break;
137 }
138 }
139
140 if first_is_verb {
144 return None;
145 }
146
147 if !found_verb {
149 return None;
150 }
151
152 if prefix_end == 0 {
153 return None;
154 }
155
156 let prefix_parts: Vec<&str> = parts[..prefix_end].iter().map(|s| s.as_str()).collect();
157 let prefix = prefix_parts.join("");
158
159 Some(to_snake_case(&prefix))
160}
161
162#[proc_macro_derive(Action, attributes(action))]
194pub fn derive_action(input: TokenStream) -> TokenStream {
195 let input = parse_macro_input!(input as DeriveInput);
196
197 let opts = match ActionOpts::from_derive_input(&input) {
199 Ok(opts) => opts,
200 Err(e) => return e.write_errors().into(),
201 };
202
203 let name = &opts.ident;
204
205 let variants = match &opts.data {
206 darling::ast::Data::Enum(variants) => variants,
207 _ => {
208 return syn::Error::new_spanned(&input, "Action can only be derived for enums")
209 .to_compile_error()
210 .into();
211 }
212 };
213
214 let syn_variants = match &input.data {
216 syn::Data::Enum(data) => &data.variants,
217 _ => unreachable!(), };
219
220 let name_arms = variants.iter().map(|v| {
222 let variant_name = &v.ident;
223 let variant_str = variant_name.to_string();
224
225 match &v.fields.style {
226 darling::ast::Style::Unit => quote! {
227 #name::#variant_name => #variant_str
228 },
229 darling::ast::Style::Tuple => quote! {
230 #name::#variant_name(..) => #variant_str
231 },
232 darling::ast::Style::Struct => quote! {
233 #name::#variant_name { .. } => #variant_str
234 },
235 }
236 });
237
238 let params_arms = syn_variants.iter().map(|v| {
240 let variant_name = &v.ident;
241
242 match &v.fields {
243 syn::Fields::Unit => quote! {
244 #name::#variant_name => ::std::string::String::new()
245 },
246 syn::Fields::Unnamed(fields) => {
247 let field_count = fields.unnamed.len();
248 let field_names: Vec<_> =
249 (0..field_count).map(|i| format_ident!("_{}", i)).collect();
250 if field_count == 1 {
251 quote! {
252 #name::#variant_name(#(#field_names),*) => {
253 tui_dispatch::debug::ron_string(&#(#field_names),*)
254 }
255 }
256 } else {
257 let parts = field_names.iter().map(|field| {
258 quote! { tui_dispatch::debug::ron_string(&#field) }
259 });
260 quote! {
261 #name::#variant_name(#(#field_names),*) => {
262 let values = ::std::vec![#(#parts),*];
263 format!("({})", values.join(", "))
264 }
265 }
266 }
267 }
268 syn::Fields::Named(fields) => {
269 let field_names: Vec<_> = fields
270 .named
271 .iter()
272 .filter_map(|f| f.ident.as_ref())
273 .collect();
274 if field_names.is_empty() {
275 quote! {
276 #name::#variant_name { .. } => ::std::string::String::new()
277 }
278 } else {
279 let parts = field_names.iter().map(|field| {
280 let label = field.to_string();
281 quote! {
282 format!("{}: {}", #label, tui_dispatch::debug::ron_string(&#field))
283 }
284 });
285 quote! {
286 #name::#variant_name { #(#field_names),*, .. } => {
287 let values = ::std::vec![#(#parts),*];
288 format!("{{{}}}", values.join(", "))
289 }
290 }
291 }
292 }
293 }
294 });
295
296 let params_pretty_arms = syn_variants.iter().map(|v| {
297 let variant_name = &v.ident;
298
299 match &v.fields {
300 syn::Fields::Unit => quote! {
301 #name::#variant_name => ::std::string::String::new()
302 },
303 syn::Fields::Unnamed(fields) => {
304 let field_count = fields.unnamed.len();
305 let field_names: Vec<_> =
306 (0..field_count).map(|i| format_ident!("_{}", i)).collect();
307 if field_count == 1 {
308 quote! {
309 #name::#variant_name(#(#field_names),*) => {
310 tui_dispatch::debug::ron_string_pretty(&#(#field_names),*)
311 }
312 }
313 } else {
314 let parts = field_names.iter().map(|field| {
315 quote! { tui_dispatch::debug::ron_string_pretty(&#field) }
316 });
317 quote! {
318 #name::#variant_name(#(#field_names),*) => {
319 let values = ::std::vec![#(#parts),*];
320 format!("({})", values.join(", "))
321 }
322 }
323 }
324 }
325 syn::Fields::Named(fields) => {
326 let field_names: Vec<_> = fields
327 .named
328 .iter()
329 .filter_map(|f| f.ident.as_ref())
330 .collect();
331 if field_names.is_empty() {
332 quote! {
333 #name::#variant_name { .. } => ::std::string::String::new()
334 }
335 } else {
336 let parts = field_names.iter().map(|field| {
337 let label = field.to_string();
338 quote! {
339 format!("{}: {}", #label, tui_dispatch::debug::ron_string_pretty(&#field))
340 }
341 });
342 quote! {
343 #name::#variant_name { #(#field_names),*, .. } => {
344 let values = ::std::vec![#(#parts),*];
345 format!("{{{}}}", values.join(", "))
346 }
347 }
348 }
349 }
350 }
351 });
352
353 let mut expanded = quote! {
354 impl tui_dispatch::Action for #name {
355 fn name(&self) -> &'static str {
356 match self {
357 #(#name_arms),*
358 }
359 }
360 }
361
362 impl tui_dispatch::ActionParams for #name {
363 fn params(&self) -> ::std::string::String {
364 match self {
365 #(#params_arms),*
366 }
367 }
368
369 fn params_pretty(&self) -> ::std::string::String {
370 match self {
371 #(#params_pretty_arms),*
372 }
373 }
374 }
375 };
376
377 if opts.infer_categories {
379 let mut categories: HashMap<String, Vec<&Ident>> = HashMap::new();
381 let mut variant_categories: Vec<(&Ident, Option<String>)> = Vec::new();
382
383 for v in variants.iter() {
384 let cat = if v.skip_category {
385 None
386 } else if let Some(ref explicit_cat) = v.category {
387 Some(explicit_cat.clone())
388 } else {
389 infer_category(&v.ident.to_string())
390 };
391
392 variant_categories.push((&v.ident, cat.clone()));
393
394 if let Some(ref category) = cat {
395 categories
396 .entry(category.clone())
397 .or_default()
398 .push(&v.ident);
399 }
400 }
401
402 let mut sorted_categories: Vec<_> = categories.keys().cloned().collect();
404 sorted_categories.sort();
405
406 let category_arms_dedup: Vec<_> = variant_categories
408 .iter()
409 .map(|(variant, cat)| {
410 let cat_expr = match cat {
411 Some(c) => quote! { ::core::option::Option::Some(#c) },
412 None => quote! { ::core::option::Option::None },
413 };
414 quote! { #name::#variant { .. } => #cat_expr }
416 })
417 .collect();
418
419 let category_enum_name = format_ident!("{}Category", name);
421 let category_variants: Vec<_> = sorted_categories
422 .iter()
423 .map(|c| format_ident!("{}", to_pascal_case(c)))
424 .collect();
425 let category_variant_names: Vec<_> = sorted_categories.clone();
426
427 let category_enum_arms: Vec<_> = variant_categories
429 .iter()
430 .map(|(variant, cat)| {
431 let cat_variant = match cat {
432 Some(c) => format_ident!("{}", to_pascal_case(c)),
433 None => format_ident!("Uncategorized"),
434 };
435 quote! { #name::#variant { .. } => #category_enum_name::#cat_variant }
436 })
437 .collect();
438
439 let predicates: Vec<_> = sorted_categories
441 .iter()
442 .map(|cat| {
443 let predicate_name = format_ident!("is_{}", cat);
444 let cat_variants = categories.get(cat).unwrap();
445 let patterns: Vec<_> = cat_variants
446 .iter()
447 .map(|v| quote! { #name::#v { .. } })
448 .collect();
449 let doc = format!(
450 "Returns true if this action belongs to the `{}` category.",
451 cat
452 );
453
454 quote! {
455 #[doc = #doc]
456 pub fn #predicate_name(&self) -> bool {
457 matches!(self, #(#patterns)|*)
458 }
459 }
460 })
461 .collect();
462
463 let category_enum_doc = format!(
465 "Action categories for [`{}`].\n\n\
466 Use [`{}::category_enum()`] to get the category of an action.",
467 name, name
468 );
469
470 expanded = quote! {
471 #expanded
472
473 #[doc = #category_enum_doc]
474 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
475 pub enum #category_enum_name {
476 #(#category_variants,)*
477 Uncategorized,
479 }
480
481 impl #category_enum_name {
482 pub fn all() -> &'static [Self] {
484 &[#(Self::#category_variants,)* Self::Uncategorized]
485 }
486
487 pub fn name(&self) -> &'static str {
489 match self {
490 #(Self::#category_variants => #category_variant_names,)*
491 Self::Uncategorized => "uncategorized",
492 }
493 }
494 }
495
496 impl #name {
497 pub fn category(&self) -> ::core::option::Option<&'static str> {
499 match self {
500 #(#category_arms_dedup,)*
501 }
502 }
503
504 pub fn category_enum(&self) -> #category_enum_name {
506 match self {
507 #(#category_enum_arms,)*
508 }
509 }
510
511 #(#predicates)*
512 }
513
514 impl tui_dispatch::ActionCategory for #name {
515 type Category = #category_enum_name;
516
517 fn category(&self) -> ::core::option::Option<&'static str> {
518 #name::category(self)
519 }
520
521 fn category_enum(&self) -> Self::Category {
522 #name::category_enum(self)
523 }
524 }
525 };
526
527 if opts.generate_dispatcher {
529 let dispatcher_trait_name = format_ident!("{}Dispatcher", name);
530
531 let dispatch_methods: Vec<_> = sorted_categories
532 .iter()
533 .map(|cat| {
534 let method_name = format_ident!("dispatch_{}", cat);
535 let doc = format!("Handle actions in the `{}` category.", cat);
536 quote! {
537 #[doc = #doc]
538 fn #method_name(&mut self, action: &#name) -> bool {
539 false
540 }
541 }
542 })
543 .collect();
544
545 let dispatch_arms: Vec<_> = sorted_categories
546 .iter()
547 .map(|cat| {
548 let method_name = format_ident!("dispatch_{}", cat);
549 let cat_variant = format_ident!("{}", to_pascal_case(cat));
550 quote! {
551 #category_enum_name::#cat_variant => self.#method_name(action)
552 }
553 })
554 .collect();
555
556 let dispatcher_doc = format!(
557 "Dispatcher trait for [`{}`].\n\n\
558 Implement the `dispatch_*` methods for each category you want to handle.\n\
559 The [`dispatch()`](Self::dispatch) method automatically routes to the correct handler.",
560 name
561 );
562
563 expanded = quote! {
564 #expanded
565
566 #[doc = #dispatcher_doc]
567 pub trait #dispatcher_trait_name {
568 #(#dispatch_methods)*
569
570 fn dispatch_uncategorized(&mut self, action: &#name) -> bool {
572 false
573 }
574
575 fn dispatch(&mut self, action: &#name) -> bool {
577 match action.category_enum() {
578 #(#dispatch_arms,)*
579 #category_enum_name::Uncategorized => self.dispatch_uncategorized(action),
580 }
581 }
582 }
583 };
584 }
585 }
586
587 TokenStream::from(expanded)
588}
589
590#[proc_macro_derive(BindingContext)]
609pub fn derive_binding_context(input: TokenStream) -> TokenStream {
610 let input = parse_macro_input!(input as DeriveInput);
611 let name = &input.ident;
612
613 let expanded = match &input.data {
614 syn::Data::Enum(data) => {
615 for variant in &data.variants {
617 if !matches!(variant.fields, syn::Fields::Unit) {
618 return syn::Error::new_spanned(
619 variant,
620 "BindingContext can only be derived for enums with unit variants",
621 )
622 .to_compile_error()
623 .into();
624 }
625 }
626
627 let variant_names: Vec<_> = data.variants.iter().map(|v| &v.ident).collect();
628 let variant_strings: Vec<_> = variant_names
629 .iter()
630 .map(|v| to_snake_case(&v.to_string()))
631 .collect();
632
633 let name_arms = variant_names
634 .iter()
635 .zip(variant_strings.iter())
636 .map(|(v, s)| {
637 quote! { #name::#v => #s }
638 });
639
640 let from_name_arms = variant_names
641 .iter()
642 .zip(variant_strings.iter())
643 .map(|(v, s)| {
644 quote! { #s => ::core::option::Option::Some(#name::#v) }
645 });
646
647 let all_variants = variant_names.iter().map(|v| quote! { #name::#v });
648
649 quote! {
650 impl tui_dispatch::BindingContext for #name {
651 fn name(&self) -> &'static str {
652 match self {
653 #(#name_arms),*
654 }
655 }
656
657 fn from_name(name: &str) -> ::core::option::Option<Self> {
658 match name {
659 #(#from_name_arms,)*
660 _ => ::core::option::Option::None,
661 }
662 }
663
664 fn all() -> &'static [Self] {
665 static ALL: &[#name] = &[#(#all_variants),*];
666 ALL
667 }
668 }
669 }
670 }
671 _ => {
672 return syn::Error::new_spanned(input, "BindingContext can only be derived for enums")
673 .to_compile_error()
674 .into();
675 }
676 };
677
678 TokenStream::from(expanded)
679}
680
681#[proc_macro_derive(ComponentId)]
697pub fn derive_component_id(input: TokenStream) -> TokenStream {
698 let input = parse_macro_input!(input as DeriveInput);
699 let name = &input.ident;
700
701 let expanded = match &input.data {
702 syn::Data::Enum(data) => {
703 for variant in &data.variants {
705 if !matches!(variant.fields, syn::Fields::Unit) {
706 return syn::Error::new_spanned(
707 variant,
708 "ComponentId can only be derived for enums with unit variants",
709 )
710 .to_compile_error()
711 .into();
712 }
713 }
714
715 let variant_names: Vec<_> = data.variants.iter().map(|v| &v.ident).collect();
716 let variant_strings: Vec<_> = variant_names.iter().map(|v| v.to_string()).collect();
717
718 let name_arms = variant_names
719 .iter()
720 .zip(variant_strings.iter())
721 .map(|(v, s)| {
722 quote! { #name::#v => #s }
723 });
724
725 quote! {
726 impl tui_dispatch::ComponentId for #name {
727 fn name(&self) -> &'static str {
728 match self {
729 #(#name_arms),*
730 }
731 }
732 }
733 }
734 }
735 _ => {
736 return syn::Error::new_spanned(input, "ComponentId can only be derived for enums")
737 .to_compile_error()
738 .into();
739 }
740 };
741
742 TokenStream::from(expanded)
743}
744
745#[derive(Debug, FromDeriveInput)]
751#[darling(attributes(debug_state), supports(struct_named))]
752struct DebugStateOpts {
753 ident: syn::Ident,
754 data: darling::ast::Data<(), DebugStateField>,
755}
756
757#[derive(Debug, FromField)]
759#[darling(attributes(debug))]
760struct DebugStateField {
761 ident: Option<syn::Ident>,
762
763 #[darling(default)]
765 section: Option<String>,
766
767 #[darling(default)]
769 skip: bool,
770
771 #[darling(default)]
773 format: Option<String>,
774
775 #[darling(default)]
777 label: Option<String>,
778
779 #[darling(default)]
781 debug_fmt: bool,
782}
783
784#[proc_macro_derive(DebugState, attributes(debug, debug_state))]
822pub fn derive_debug_state(input: TokenStream) -> TokenStream {
823 let input = parse_macro_input!(input as DeriveInput);
824
825 let opts = match DebugStateOpts::from_derive_input(&input) {
826 Ok(opts) => opts,
827 Err(e) => return e.write_errors().into(),
828 };
829
830 let name = &opts.ident;
831 let default_section = name.to_string();
832
833 let fields = match &opts.data {
834 darling::ast::Data::Struct(fields) => fields,
835 _ => {
836 return syn::Error::new_spanned(&input, "DebugState can only be derived for structs")
837 .to_compile_error()
838 .into();
839 }
840 };
841
842 let mut sections: HashMap<String, Vec<&DebugStateField>> = HashMap::new();
844 let mut section_order: Vec<String> = Vec::new();
845
846 for field in fields.iter() {
847 if field.skip {
848 continue;
849 }
850
851 let section_name = field
852 .section
853 .clone()
854 .unwrap_or_else(|| default_section.clone());
855
856 if !section_order.contains(§ion_name) {
857 section_order.push(section_name.clone());
858 }
859
860 sections.entry(section_name).or_default().push(field);
861 }
862
863 let section_code: Vec<_> = section_order
865 .iter()
866 .map(|section_name| {
867 let fields_in_section = sections.get(section_name).unwrap();
868
869 let entry_calls: Vec<_> = fields_in_section
870 .iter()
871 .filter_map(|field| {
872 let field_ident = field.ident.as_ref()?;
873 let label = field
874 .label
875 .clone()
876 .unwrap_or_else(|| field_ident.to_string());
877
878 let value_expr = if let Some(ref fmt) = field.format {
879 quote! { format!(#fmt, self.#field_ident) }
880 } else if field.debug_fmt {
881 quote! { format!("{:?}", self.#field_ident) }
882 } else {
883 quote! { tui_dispatch::debug::ron_string(&self.#field_ident) }
884 };
885
886 Some(quote! {
887 .entry(#label, #value_expr)
888 })
889 })
890 .collect();
891
892 quote! {
893 tui_dispatch::debug::DebugSection::new(#section_name)
894 #(#entry_calls)*
895 }
896 })
897 .collect();
898
899 let expanded = quote! {
900 impl tui_dispatch::debug::DebugState for #name {
901 fn debug_sections(&self) -> ::std::vec::Vec<tui_dispatch::debug::DebugSection> {
902 ::std::vec![
903 #(#section_code),*
904 ]
905 }
906 }
907 };
908
909 TokenStream::from(expanded)
910}
911
912#[derive(Debug, FromField)]
918#[darling(attributes(flag))]
919struct FeatureFlagsField {
920 ident: Option<syn::Ident>,
921 ty: syn::Type,
922
923 #[darling(default)]
925 default: Option<bool>,
926}
927
928#[derive(Debug, FromDeriveInput)]
930#[darling(attributes(feature_flags), supports(struct_named))]
931struct FeatureFlagsOpts {
932 ident: syn::Ident,
933 data: darling::ast::Data<(), FeatureFlagsField>,
934}
935
936#[proc_macro_derive(FeatureFlags, attributes(flag, feature_flags))]
967pub fn derive_feature_flags(input: TokenStream) -> TokenStream {
968 let input = parse_macro_input!(input as DeriveInput);
969
970 let opts = match FeatureFlagsOpts::from_derive_input(&input) {
971 Ok(opts) => opts,
972 Err(e) => return e.write_errors().into(),
973 };
974
975 let name = &opts.ident;
976
977 let fields = match &opts.data {
978 darling::ast::Data::Struct(fields) => fields,
979 _ => {
980 return syn::Error::new_spanned(
981 &input,
982 "FeatureFlags can only be derived for structs with named fields",
983 )
984 .to_compile_error()
985 .into();
986 }
987 };
988
989 let bool_fields: Vec<_> = fields
991 .iter()
992 .filter_map(|f| {
993 let ident = f.ident.as_ref()?;
994 if let syn::Type::Path(type_path) = &f.ty {
996 if type_path.path.is_ident("bool") {
997 return Some((ident.clone(), f.default.unwrap_or(false)));
998 }
999 }
1000 None
1001 })
1002 .collect();
1003
1004 if bool_fields.is_empty() {
1005 return syn::Error::new_spanned(
1006 &input,
1007 "FeatureFlags struct must have at least one bool field",
1008 )
1009 .to_compile_error()
1010 .into();
1011 }
1012
1013 let is_enabled_arms: Vec<_> = bool_fields
1015 .iter()
1016 .map(|(ident, _)| {
1017 let name_str = ident.to_string();
1018 quote! { #name_str => ::core::option::Option::Some(self.#ident) }
1019 })
1020 .collect();
1021
1022 let set_arms: Vec<_> = bool_fields
1024 .iter()
1025 .map(|(ident, _)| {
1026 let name_str = ident.to_string();
1027 quote! {
1028 #name_str => {
1029 self.#ident = enabled;
1030 true
1031 }
1032 }
1033 })
1034 .collect();
1035
1036 let flag_names: Vec<_> = bool_fields
1038 .iter()
1039 .map(|(ident, _)| ident.to_string())
1040 .collect();
1041
1042 let default_fields: Vec<_> = bool_fields
1044 .iter()
1045 .map(|(ident, default)| {
1046 quote! { #ident: #default }
1047 })
1048 .collect();
1049
1050 let expanded = quote! {
1051 impl tui_dispatch::FeatureFlags for #name {
1052 fn is_enabled(&self, name: &str) -> ::core::option::Option<bool> {
1053 match name {
1054 #(#is_enabled_arms,)*
1055 _ => ::core::option::Option::None,
1056 }
1057 }
1058
1059 fn set(&mut self, name: &str, enabled: bool) -> bool {
1060 match name {
1061 #(#set_arms)*
1062 _ => false,
1063 }
1064 }
1065
1066 fn all_flags() -> &'static [&'static str] {
1067 &[#(#flag_names),*]
1068 }
1069 }
1070
1071 impl ::core::default::Default for #name {
1072 fn default() -> Self {
1073 Self {
1074 #(#default_fields,)*
1075 }
1076 }
1077 }
1078 };
1079
1080 TokenStream::from(expanded)
1081}