1#![deny(unused)]
7
8use proc_macro2::{Ident, TokenStream};
9use quote::{format_ident, quote};
10use syn::{
11 parse_macro_input, parse_quote, Data, DeriveInput, Expr, Fields, GenericParam, Generics, Lit,
12 Meta, Path, Result, Type, WherePredicate,
13};
14
15mod attr;
16mod utils;
17
18use attr::{ContainerAttr, FieldAttr, VariantAttr};
19
20struct Dependencies {
22 crate_rename: Path,
23 types: Vec<Type>,
25 transitive: Vec<Type>,
27 generics: Vec<Type>,
29}
30
31impl Dependencies {
32 fn new(crate_rename: Path) -> Self {
33 Self {
34 crate_rename,
35 types: Vec::new(),
36 transitive: Vec::new(),
37 generics: Vec::new(),
38 }
39 }
40
41 fn push(&mut self, ty: &Type) {
43 self.types.push(ty.clone());
44 self.generics.push(ty.clone());
45 }
46
47 fn append_from(&mut self, ty: &Type) {
49 self.transitive.push(ty.clone());
50 }
51
52 fn to_tokens(&self) -> TokenStream {
53 let crate_rename = &self.crate_rename;
54 let visit_types = self.types.iter().map(|ty| {
55 quote! { v.visit::<#ty>(); }
56 });
57 let visit_transitive = self.transitive.iter().map(|ty| {
58 quote! { <#ty as #crate_rename::Flow>::visit_dependencies(v); }
59 });
60 let visit_generics = self.generics.iter().map(|ty| {
61 quote! { <#ty as #crate_rename::Flow>::visit_generics(v); }
62 });
63
64 quote! {
65 #(#visit_types)*
66 #(#visit_generics)*
67 #(#visit_transitive)*
68 }
69 }
70}
71
72struct DerivedFlow {
73 crate_rename: Path,
74 flow_name: Expr,
75 docs: Vec<Expr>,
76 inline: TokenStream,
77 inline_flattened: TokenStream,
78 is_enum: TokenStream,
79 is_opaque: bool,
80 opaque_bound: Option<TokenStream>,
81 export: bool,
82 export_to: Option<Expr>,
83 bound: Option<Vec<WherePredicate>>,
84 deps: Dependencies,
85}
86
87impl DerivedFlow {
88 fn into_impl(self, rust_ty: Ident, generics: Generics) -> TokenStream {
89 let export_test = self
90 .export
91 .then(|| self.generate_export_test(&rust_ty, &generics));
92
93 let output_path_fn = {
94 let flow_name = &self.flow_name;
95 let path_string = match &self.export_to {
96 Some(dir_or_file) => quote! {{
97 let dir_or_file = format!("{}", #dir_or_file);
98 if dir_or_file.ends_with('/') {
99 format!("{dir_or_file}{}.js.flow", #flow_name)
100 } else {
101 format!("{dir_or_file}")
102 }
103 }},
104 None => quote![format!("{}.js.flow", #flow_name)],
105 };
106 quote! {
107 fn output_path() -> Option<std::path::PathBuf> {
108 Some(std::path::PathBuf::from(#path_string))
109 }
110 }
111 };
112
113 let crate_rename = &self.crate_rename;
114 let flow_name = &self.flow_name;
115 let inline = &self.inline;
116 let inline_flattened = &self.inline_flattened;
117 let is_enum = &self.is_enum;
118
119 let docs_fn = if self.docs.is_empty() {
120 quote! { fn docs() -> Option<String> { None } }
121 } else {
122 let docs = &self.docs;
123 quote! {
124 fn docs() -> Option<String> {
125 Some([#(#docs),*].join("\n"))
126 }
127 }
128 };
129
130 let name_fn = {
132 let generic_names: Vec<_> = generics
133 .type_params()
134 .map(|tp| {
135 let ident = &tp.ident;
136 quote!(<#ident as #crate_rename::Flow>::name(cfg))
137 })
138 .collect();
139
140 if generic_names.is_empty() {
141 quote! {
142 fn name(cfg: &#crate_rename::Config) -> String {
143 #flow_name.to_owned()
144 }
145 }
146 } else {
147 quote! {
148 fn name(cfg: &#crate_rename::Config) -> String {
149 format!("{}<{}>", #flow_name, vec![#(#generic_names),*].join(", "))
150 }
151 }
152 }
153 };
154
155 let decl_fn = if self.is_opaque {
157 let bound = self
158 .opaque_bound
159 .map(|b| quote! { format!("declare export opaque type {}: {};", #flow_name, #b) })
160 .unwrap_or_else(
161 || quote! { format!("declare export opaque type {};", #flow_name) },
162 );
163 quote! {
164 fn decl(cfg: &#crate_rename::Config) -> String {
165 #bound
166 }
167 }
168 } else {
169 let has_generics = generics.type_params().next().is_some();
170 if has_generics {
171 let generic_idents: Vec<_> = generics.type_params().map(|tp| &tp.ident).collect();
173
174 let dummy_decls: Vec<_> = generic_idents
176 .iter()
177 .map(|ident| {
178 let dummy_name = format_ident!("{}Dummy", ident);
179 quote! {
180 struct #dummy_name;
181 impl #crate_rename::Flow for #dummy_name {
182 type WithoutGenerics = Self;
183 type OptionInnerType = Self;
184 fn name(_: &#crate_rename::Config) -> String {
185 stringify!(#ident).to_owned()
186 }
187 fn inline(cfg: &#crate_rename::Config) -> String {
188 Self::name(cfg)
189 }
190 }
191 }
192 })
193 .collect();
194
195 let generics_str: Vec<_> = generic_idents
196 .iter()
197 .map(|ident| quote!(stringify!(#ident)))
198 .collect();
199
200 let full_generic_args: Vec<_> = generics
202 .params
203 .iter()
204 .map(|p| match p {
205 GenericParam::Type(tp) => {
206 let dummy_name = format_ident!("{}Dummy", tp.ident);
207 quote!(#dummy_name)
208 }
209 GenericParam::Lifetime(lt) => {
210 let lt = <.lifetime;
211 quote!(#lt)
212 }
213 GenericParam::Const(c) => {
214 let ident = &c.ident;
215 quote!(#ident)
216 }
217 })
218 .collect();
219
220 quote! {
221 fn decl(cfg: &#crate_rename::Config) -> String {
222 #(#dummy_decls)*
225 let inline = <#rust_ty<#(#full_generic_args),*> as #crate_rename::Flow>::inline(cfg);
226 let generics = format!("<{}>", vec![#(#generics_str.to_owned()),*].join(", "));
227 format!("type {}{generics} = {inline};", #flow_name)
228 }
229 }
230 } else {
231 quote! {
232 fn decl(cfg: &#crate_rename::Config) -> String {
233 format!("type {} = {};", Self::name(cfg), Self::inline(cfg))
234 }
235 }
236 }
237 };
238
239 let decl_concrete_fn = if self.is_opaque {
241 quote! {
242 fn decl_concrete(cfg: &#crate_rename::Config) -> String {
243 Self::decl(cfg)
244 }
245 }
246 } else {
247 quote! {
248 fn decl_concrete(cfg: &#crate_rename::Config) -> String {
249 format!("type {} = {};", Self::name(cfg), Self::inline(cfg))
250 }
251 }
252 };
253
254 let mut bounds = generics.clone();
256 if let Some(extra) = &self.bound {
257 let where_clause = bounds.make_where_clause();
258 for pred in extra {
259 where_clause.predicates.push(pred.clone());
260 }
261 }
262 for param in &generics.params {
264 if let GenericParam::Type(tp) = param {
265 let ident = &tp.ident;
266 let where_clause = bounds.make_where_clause();
267 where_clause
268 .predicates
269 .push(parse_quote!(#ident: #crate_rename::Flow));
270 }
271 }
272 let (impl_generics, ty_generics, where_clause) = bounds.split_for_impl();
273
274 let without_generics = if generics.params.is_empty() {
276 quote!(Self)
277 } else {
278 let params = generics.params.iter().map(|p| match p {
279 GenericParam::Type(_) => quote!(#crate_rename::Dummy),
280 GenericParam::Lifetime(lt) => {
281 let lt = <.lifetime;
282 quote!(#lt)
283 }
284 GenericParam::Const(c) => {
285 let ident = &c.ident;
286 quote!(#ident)
287 }
288 });
289 quote!(#rust_ty<#(#params),*>)
290 };
291
292 let dep_tokens = self.deps.to_tokens();
294 let visit_deps_fn = quote! {
295 fn visit_dependencies(v: &mut impl #crate_rename::TypeVisitor)
296 where
297 Self: 'static,
298 {
299 #dep_tokens
300 }
301 };
302
303 let visit_generics_fn = {
305 let generic_visits: Vec<_> = generics
306 .type_params()
307 .map(|tp| {
308 let ident = &tp.ident;
309 quote! {
310 v.visit::<#ident>();
311 <#ident as #crate_rename::Flow>::visit_generics(v);
312 }
313 })
314 .collect();
315
316 quote! {
317 fn visit_generics(v: &mut impl #crate_rename::TypeVisitor)
318 where
319 Self: 'static,
320 {
321 #(#generic_visits)*
322 }
323 }
324 };
325
326 let inline_flattened_fn = quote! {
328 fn inline_flattened(cfg: &#crate_rename::Config) -> String {
329 #inline_flattened
330 }
331 };
332
333 quote! {
334 #[automatically_derived]
335 impl #impl_generics #crate_rename::Flow for #rust_ty #ty_generics #where_clause {
336 type WithoutGenerics = #without_generics;
337 type OptionInnerType = Self;
338
339 #name_fn
340
341 fn inline(cfg: &#crate_rename::Config) -> String {
342 #inline
343 }
344
345 #inline_flattened_fn
346 #decl_fn
347 #decl_concrete_fn
348 #docs_fn
349 #output_path_fn
350 #visit_deps_fn
351 #visit_generics_fn
352
353 const IS_ENUM: bool = #is_enum;
354 }
355
356 #export_test
357 }
358 }
359
360 fn generate_export_test(&self, rust_ty: &Ident, generics: &Generics) -> TokenStream {
361 let crate_rename = &self.crate_rename;
362 let test_name = format_ident!("export_flow_bindings_{}", rust_ty);
363 let ty = if generics.params.is_empty() {
364 quote!(#rust_ty)
365 } else {
366 let dummies = generics.params.iter().map(|p| match p {
367 GenericParam::Type(_) => quote!(#crate_rename::Dummy),
368 GenericParam::Lifetime(lt) => {
369 let lt = <.lifetime;
370 quote!(#lt)
371 }
372 GenericParam::Const(c) => {
373 let ident = &c.ident;
374 quote!(#ident)
375 }
376 });
377 quote!(#rust_ty<#(#dummies),*>)
378 };
379
380 quote! {
381 #[cfg(test)]
382 #[test]
383 #[allow(non_snake_case)]
384 fn #test_name() {
385 let cfg = #crate_rename::Config::from_env();
386 <#ty as #crate_rename::Flow>::export_all(&cfg)
387 .expect("could not export type");
388 }
389 }
390 }
391}
392
393#[proc_macro_derive(Flow, attributes(flow))]
415pub fn derive_flow(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
416 let input = parse_macro_input!(input as DeriveInput);
417 match derive_flow_impl(input) {
418 Ok(tokens) => tokens.into(),
419 Err(err) => err.to_compile_error().into(),
420 }
421}
422
423fn derive_flow_impl(input: DeriveInput) -> Result<TokenStream> {
424 let container = ContainerAttr::from_attrs(&input.attrs)?;
425 let ident = &input.ident;
426
427 let crate_rename = container
428 .crate_rename
429 .clone()
430 .unwrap_or_else(|| parse_quote!(::flowjs_rs));
431
432 let flow_name: Expr = match &container.rename {
433 Some(name) => parse_quote!(#name),
434 None => {
435 let name = ident.to_string();
436 parse_quote!(#name)
437 }
438 };
439
440 let docs: Vec<Expr> = input
441 .attrs
442 .iter()
443 .filter_map(|attr| {
444 if !attr.path().is_ident("doc") {
445 return None;
446 }
447 if let Meta::NameValue(nv) = &attr.meta {
448 if let Expr::Lit(lit) = &nv.value {
449 if let Lit::Str(s) = &lit.lit {
450 let val = s.value();
451 let trimmed = val.trim();
452 return Some(parse_quote!(#trimmed));
453 }
454 }
455 }
456 None
457 })
458 .collect();
459
460 let mut deps = Dependencies::new(crate_rename.clone());
461
462 let (inline, inline_flattened, is_enum) = match &input.data {
463 Data::Struct(data) => {
464 let (inline, flattened) =
465 derive_struct(&crate_rename, &container, &data.fields, &mut deps)?;
466 (inline, flattened, quote!(false))
467 }
468 Data::Enum(data) => {
469 let inline = derive_enum(&crate_rename, &container, data, &mut deps)?;
470 let flattened = quote! {
471 format!("({})", Self::inline(cfg))
472 };
473 (inline, flattened, quote!(true))
474 }
475 Data::Union(_) => {
476 return Err(syn::Error::new(
477 ident.span(),
478 "Flow cannot be derived for unions",
479 ));
480 }
481 };
482
483 let (is_opaque, opaque_bound) = match &container.opaque {
484 Some(Some(bound)) => (true, Some(quote!(#bound))),
485 Some(None) => (true, None),
486 None => (false, None),
487 };
488
489 let derived = DerivedFlow {
490 crate_rename,
491 flow_name,
492 docs,
493 inline,
494 inline_flattened,
495 is_enum,
496 is_opaque,
497 opaque_bound,
498 export: container.export,
499 export_to: container.export_to.clone(),
500 bound: container.bound.clone(),
501 deps,
502 };
503
504 Ok(derived.into_impl(ident.clone(), input.generics.clone()))
505}
506
507fn derive_struct(
508 crate_rename: &Path,
509 container: &ContainerAttr,
510 fields: &Fields,
511 deps: &mut Dependencies,
512) -> Result<(TokenStream, TokenStream)> {
513 match fields {
514 Fields::Named(named) => {
515 let mut formatted_fields: Vec<TokenStream> = Vec::new();
516 let mut flattened_fields: Vec<TokenStream> = Vec::new();
517
518 for f in &named.named {
519 let field_attr = FieldAttr::from_attrs(&f.attrs)?;
520 if field_attr.skip {
521 continue;
522 }
523
524 let field_name = f.ident.as_ref().unwrap();
525 let ty = &f.ty;
526
527 if field_attr.flatten {
528 if field_attr.type_override.is_none() {
530 deps.append_from(ty);
531 }
532 flattened_fields
533 .push(quote!(<#ty as #crate_rename::Flow>::inline_flattened(cfg)));
534 continue;
535 }
536
537 let name =
538 utils::quote_property_name(&field_attr.rename.clone().unwrap_or_else(|| {
539 let raw = field_name.to_string();
540 container.rename_field(&raw)
541 }));
542
543 let effective_ty = field_attr.type_as.as_ref().unwrap_or(ty);
546
547 if field_attr.type_override.is_none() {
549 if field_attr.inline {
550 deps.append_from(effective_ty);
551 } else {
552 deps.push(effective_ty);
553 }
554 }
555
556 let type_str = if let Some(override_ty) = &field_attr.type_override {
557 quote!(#override_ty.to_owned())
558 } else if field_attr.inline {
559 quote!(<#effective_ty as #crate_rename::Flow>::inline(cfg))
560 } else {
561 quote!(<#effective_ty as #crate_rename::Flow>::name(cfg))
562 };
563
564 let is_omittable = field_attr.optional || field_attr.is_serde_optional();
568 let opt_marker = if is_omittable { "?" } else { "" };
569
570 formatted_fields.push(quote! {
571 format!(" +{}{}: {},", #name, #opt_marker, #type_str)
572 });
573 }
574
575 let inline = match (formatted_fields.len(), flattened_fields.len()) {
577 (0, 0) => quote!("{||}".to_owned()),
578 (_, 0) => quote! {{
579 let fields = vec![#(#formatted_fields),*];
580 format!("{{|\n{}\n|}}", fields.join("\n"))
581 }},
582 (0, 1) => {
583 let flat = &flattened_fields[0];
584 quote! {{
585 let f = #flat;
586 if f.starts_with('(') && f.ends_with(')') {
587 f[1..f.len() - 1].trim().to_owned()
588 } else {
589 f.trim().to_owned()
590 }
591 }}
592 }
593 (0, _) => quote! {{
594 let parts: Vec<String> = vec![#(#flattened_fields),*];
595 parts.join(" & ")
596 }},
597 (_, _) => quote! {{
598 let fields = vec![#(#formatted_fields),*];
599 let base = format!("{{|\n{}\n|}}", fields.join("\n"));
600 let flattened: Vec<String> = vec![#(#flattened_fields),*];
601 format!("{} & {}", base, flattened.join(" & "))
602 }},
603 };
604
605 let inline_flattened = match (formatted_fields.len(), flattened_fields.len()) {
607 (_, 0) => quote! {{
608 let fields = vec![#(#formatted_fields),*];
609 format!("{{|\n{}\n|}}", fields.join("\n"))
610 }},
611 (0, _) => quote! {{
612 let parts: Vec<String> = vec![#(#flattened_fields),*];
613 parts.join(" & ")
614 }},
615 (_, _) => quote! {{
616 let fields = vec![#(#formatted_fields),*];
617 let base = format!("{{|\n{}\n|}}", fields.join("\n"));
618 let flattened: Vec<String> = vec![#(#flattened_fields),*];
619 format!("{} & {}", base, flattened.join(" & "))
620 }},
621 };
622
623 Ok((inline, inline_flattened))
624 }
625 Fields::Unnamed(unnamed) => {
626 if unnamed.unnamed.len() == 1 {
627 let ty = &unnamed.unnamed[0].ty;
629 deps.push(ty);
630 let inline = quote!(<#ty as #crate_rename::Flow>::inline(cfg));
631 let flattened = quote! {
632 format!("({})", <#ty as #crate_rename::Flow>::inline(cfg))
633 };
634 Ok((inline, flattened))
635 } else {
636 let elems: Vec<TokenStream> = unnamed
638 .unnamed
639 .iter()
640 .map(|f| {
641 let ty = &f.ty;
642 deps.push(ty);
643 quote!(<#ty as #crate_rename::Flow>::inline(cfg))
644 })
645 .collect();
646 let inline = quote! {{
647 let elems: Vec<String> = vec![#(#elems),*];
648 format!("[{}]", elems.join(", "))
649 }};
650 let flattened = quote! {
651 format!("({})", Self::inline(cfg))
652 };
653 Ok((inline, flattened))
654 }
655 }
656 Fields::Unit => {
657 let inline = quote!(#crate_rename::flow_type::VOID.to_owned());
658 let flattened = quote!(#crate_rename::flow_type::VOID.to_owned());
659 Ok((inline, flattened))
660 }
661 }
662}
663
664fn derive_enum(
665 crate_rename: &Path,
666 container: &ContainerAttr,
667 data: &syn::DataEnum,
668 deps: &mut Dependencies,
669) -> Result<TokenStream> {
670 if data.variants.is_empty() {
671 return Ok(quote!(#crate_rename::flow_type::EMPTY.to_owned()));
672 }
673
674 let is_untagged = container.untagged;
675 let tag = &container.tag.as_deref().map(utils::quote_property_name);
676 let content = &container.content.as_deref().map(utils::quote_property_name);
677
678 let mut variant_defs: Vec<TokenStream> = Vec::new();
679 for v in &data.variants {
680 let variant_attr = VariantAttr::from_attrs(&v.attrs)?;
681 if variant_attr.skip {
682 continue;
683 }
684
685 let variant_name_raw = variant_attr.rename.clone().unwrap_or_else(|| {
686 let raw = v.ident.to_string();
687 container.rename_variant(&raw)
688 });
689 let variant_name = utils::escape_string_literal(&variant_name_raw);
691 let variant_key = utils::quote_property_name(&variant_name_raw);
693
694 let def = match &v.fields {
695 Fields::Unit => {
696 if is_untagged {
697 quote!(#crate_rename::flow_type::VOID.to_owned())
698 } else if let Some(tag_field) = tag {
699 quote!(format!("{{| +{}: '{}' |}}", #tag_field, #variant_name))
700 } else {
701 quote!(format!("'{}'", #variant_name))
702 }
703 }
704 Fields::Unnamed(unnamed) => {
705 if unnamed.unnamed.len() == 1 {
706 let ty = &unnamed.unnamed[0].ty;
707 deps.push(ty);
708 let inner = quote!(<#ty as #crate_rename::Flow>::inline(cfg));
709 if is_untagged {
710 inner
711 } else if let (Some(tag_field), Some(content_field)) = (tag, content) {
712 quote!(format!(
713 "{{| +{}: '{}', +{}: {} |}}",
714 #tag_field, #variant_name, #content_field, #inner
715 ))
716 } else if let Some(tag_field) = tag {
717 quote!(format!(
718 "{{| +{}: '{}' |}} & {}",
719 #tag_field, #variant_name, #inner
720 ))
721 } else {
722 quote!(format!(
723 "{{| {}: {} |}}",
724 #variant_key, #inner
725 ))
726 }
727 } else {
728 let elems: Vec<TokenStream> = unnamed
730 .unnamed
731 .iter()
732 .map(|f| {
733 let ty = &f.ty;
734 deps.push(ty);
735 quote!(<#ty as #crate_rename::Flow>::inline(cfg))
736 })
737 .collect();
738 let tuple = quote! {{
739 let elems: Vec<String> = vec![#(#elems),*];
740 format!("[{}]", elems.join(", "))
741 }};
742 if is_untagged {
743 tuple
744 } else if let (Some(tag_field), Some(content_field)) = (tag, content) {
745 quote!(format!(
746 "{{| +{}: '{}', +{}: {} |}}",
747 #tag_field, #variant_name, #content_field, #tuple
748 ))
749 } else {
750 quote!(format!(
751 "{{| {}: {} |}}",
752 #variant_key, #tuple
753 ))
754 }
755 }
756 }
757 Fields::Named(named) => {
758 let mut field_defs: Vec<TokenStream> = Vec::new();
759 let mut flattened_defs: Vec<TokenStream> = Vec::new();
760 for f in &named.named {
761 let field_attr = FieldAttr::from_attrs(&f.attrs)?;
762 if field_attr.skip {
763 continue;
764 }
765 let ty = &f.ty;
766
767 if field_attr.flatten {
768 if field_attr.type_override.is_none() {
769 deps.append_from(ty);
770 }
771 flattened_defs
772 .push(quote!(<#ty as #crate_rename::Flow>::inline_flattened(cfg)));
773 continue;
774 }
775
776 let field_name = f.ident.as_ref().unwrap();
777 let name =
778 utils::quote_property_name(&field_attr.rename.clone().unwrap_or_else(
779 || container.rename_variant_field(&field_name.to_string()),
780 ));
781 if field_attr.type_override.is_none() {
782 if field_attr.inline {
783 deps.append_from(ty);
784 } else {
785 deps.push(ty);
786 }
787 }
788 let type_str = if let Some(override_ty) = &field_attr.type_override {
789 quote!(#override_ty.to_owned())
790 } else if field_attr.inline {
791 quote!(<#ty as #crate_rename::Flow>::inline(cfg))
792 } else {
793 quote!(<#ty as #crate_rename::Flow>::name(cfg))
794 };
795 let is_omittable = field_attr.optional || field_attr.is_serde_optional();
796 let opt_marker = if is_omittable { "?" } else { "" };
797 field_defs.push(quote!(format!("+{}{}: {}", #name, #opt_marker, #type_str)));
798 }
799
800 let obj = if flattened_defs.is_empty() {
801 quote! {{
802 let fields: Vec<String> = vec![#(#field_defs),*];
803 format!("{{| {} |}}", fields.join(", "))
804 }}
805 } else if field_defs.is_empty() {
806 quote! {{
807 let parts: Vec<String> = vec![#(#flattened_defs),*];
808 parts.join(" & ")
809 }}
810 } else {
811 quote! {{
812 let fields: Vec<String> = vec![#(#field_defs),*];
813 let base = format!("{{| {} |}}", fields.join(", "));
814 let flattened: Vec<String> = vec![#(#flattened_defs),*];
815 format!("{} & {}", base, flattened.join(" & "))
816 }}
817 };
818
819 if is_untagged {
820 obj
821 } else if let (Some(tag_field), Some(content_field)) = (tag, content) {
822 quote!(format!(
823 "{{| +{}: '{}', +{}: {} |}}",
824 #tag_field, #variant_name, #content_field, #obj
825 ))
826 } else if let Some(tag_field) = tag {
827 let tag_field_def = quote!(format!("+{}: '{}'", #tag_field, #variant_name));
830
831 if flattened_defs.is_empty() {
832 let all_fields: Vec<_> = std::iter::once(tag_field_def.clone())
834 .chain(field_defs.iter().cloned())
835 .collect();
836 quote! {{
837 let fields: Vec<String> = vec![#(#all_fields),*];
838 format!("{{| {} |}}", fields.join(", "))
839 }}
840 } else {
841 let base_fields: Vec<_> = std::iter::once(tag_field_def.clone())
843 .chain(field_defs.iter().cloned())
844 .collect();
845 quote! {{
846 let fields: Vec<String> = vec![#(#base_fields),*];
847 let base = format!("{{| {} |}}", fields.join(", "));
848 let flattened: Vec<String> = vec![#(#flattened_defs),*];
849 format!("{} & {}", base, flattened.join(" & "))
850 }}
851 }
852 } else {
853 quote!(format!(
854 "{{| {}: {} |}}",
855 #variant_key, #obj
856 ))
857 }
858 }
859 };
860 variant_defs.push(def);
861 }
862
863 if variant_defs.is_empty() {
864 return Ok(quote!(#crate_rename::flow_type::EMPTY.to_owned()));
865 }
866
867 Ok(quote! {{
868 let variants: Vec<String> = vec![#(#variant_defs),*];
869 variants.join(" | ")
870 }})
871}