1#![deny(unused)]
7
8use proc_macro2::{Ident, TokenStream};
9use quote::{format_ident, quote};
10use syn::{
11 parse_macro_input, parse_quote, spanned::Spanned, Data, DeriveInput, Expr, Fields,
12 GenericParam, Generics, Lit, Meta, Path, Result, Type, WherePredicate,
13};
14
15mod attr;
16mod config;
17mod utils;
18
19use attr::{ContainerAttr, FieldAttr, FlowEnumRepr, VariantAttr};
20
21struct Dependencies {
23 crate_rename: Path,
24 types: Vec<Type>,
26 transitive: Vec<Type>,
28 generics: Vec<Type>,
30}
31
32impl Dependencies {
33 fn new(crate_rename: Path) -> Self {
34 Self {
35 crate_rename,
36 types: Vec::new(),
37 transitive: Vec::new(),
38 generics: Vec::new(),
39 }
40 }
41
42 fn push(&mut self, ty: &Type) {
44 self.types.push(ty.clone());
45 self.generics.push(ty.clone());
46 }
47
48 fn append_from(&mut self, ty: &Type) {
50 self.transitive.push(ty.clone());
51 }
52
53 fn to_tokens(&self) -> TokenStream {
54 let crate_rename = &self.crate_rename;
55 let visit_types = self.types.iter().map(|ty| {
56 quote! { v.visit::<#ty>(); }
57 });
58 let visit_transitive = self.transitive.iter().map(|ty| {
59 quote! { <#ty as #crate_rename::Flow>::visit_dependencies(v); }
60 });
61 let visit_generics = self.generics.iter().map(|ty| {
62 quote! { <#ty as #crate_rename::Flow>::visit_generics(v); }
63 });
64
65 quote! {
66 #(#visit_types)*
67 #(#visit_generics)*
68 #(#visit_transitive)*
69 }
70 }
71}
72
73struct DerivedFlow {
74 crate_rename: Path,
75 flow_name: Expr,
76 docs: Vec<Expr>,
77 inline: TokenStream,
78 inline_flattened: TokenStream,
79 is_enum: TokenStream,
80 is_opaque: bool,
81 opaque_bound: Option<TokenStream>,
82 enum_decl_override: Option<TokenStream>,
84 export: bool,
85 export_to: Option<Expr>,
86 bound: Option<Vec<WherePredicate>>,
87 deps: Dependencies,
88 concrete: std::collections::HashMap<Ident, Type>,
90}
91
92impl DerivedFlow {
93 fn into_impl(self, rust_ty: Ident, generics: Generics) -> TokenStream {
94 let export_test = self
95 .export
96 .then(|| self.generate_export_test(&rust_ty, &generics));
97
98 let output_path_fn = {
99 let flow_name = &self.flow_name;
100 let path_string = match &self.export_to {
104 Some(dir_or_file) => quote! {{
105 let dir_or_file = format!("{}", #dir_or_file);
106 if dir_or_file.ends_with('/') {
107 format!("{dir_or_file}{}", #flow_name)
109 } else {
110 format!("{dir_or_file}")
112 }
113 }},
114 None => quote![format!("{}", #flow_name)],
116 };
117 quote! {
118 fn output_path() -> Option<std::path::PathBuf> {
119 Some(std::path::PathBuf::from(#path_string))
120 }
121 }
122 };
123
124 let crate_rename = &self.crate_rename;
125 let flow_name = &self.flow_name;
126 let inline = &self.inline;
127 let inline_flattened = &self.inline_flattened;
128 let is_enum = &self.is_enum;
129
130 let docs_fn = if self.docs.is_empty() {
131 quote! { fn docs() -> Option<String> { None } }
132 } else {
133 let docs = &self.docs;
134 quote! {
135 fn docs() -> Option<String> {
136 Some([#(#docs),*].join("\n"))
137 }
138 }
139 };
140
141 let name_fn = {
143 let generic_names: Vec<_> = generics
144 .type_params()
145 .filter(|tp| !self.concrete.contains_key(&tp.ident))
146 .map(|tp| {
147 let ident = &tp.ident;
148 quote!(<#ident as #crate_rename::Flow>::name(cfg))
149 })
150 .collect();
151
152 if generic_names.is_empty() {
153 quote! {
154 fn name(cfg: &#crate_rename::Config) -> String {
155 #flow_name.to_owned()
156 }
157 }
158 } else {
159 quote! {
160 fn name(cfg: &#crate_rename::Config) -> String {
161 format!("{}<{}>", #flow_name, vec![#(#generic_names),*].join(", "))
162 }
163 }
164 }
165 };
166
167 let decl_fn = if let Some(enum_decl) = &self.enum_decl_override {
169 quote! {
170 fn decl(cfg: &#crate_rename::Config) -> String {
171 #enum_decl
172 }
173 }
174 } else if self.is_opaque {
175 let has_generics = generics.type_params().next().is_some();
179
180 if has_generics {
181 let generic_idents: Vec<_> = generics.type_params().map(|tp| &tp.ident).collect();
183 let dummy_decls: Vec<_> = generic_idents
184 .iter()
185 .map(|ident| {
186 let dummy_name = format_ident!("{}Dummy", ident);
187 quote! {
188 struct #dummy_name;
189 impl #crate_rename::Flow for #dummy_name {
190 type WithoutGenerics = Self;
191 type OptionInnerType = Self;
192 fn name(_: &#crate_rename::Config) -> String {
193 stringify!(#ident).to_owned()
194 }
195 fn inline(cfg: &#crate_rename::Config) -> String {
196 Self::name(cfg)
197 }
198 }
199 }
200 })
201 .collect();
202 let generics_str: Vec<_> = generic_idents
203 .iter()
204 .map(|ident| quote!(stringify!(#ident)))
205 .collect();
206 let full_generic_args: Vec<_> = generics
207 .params
208 .iter()
209 .map(|p| match p {
210 GenericParam::Type(tp) => {
211 let dummy_name = format_ident!("{}Dummy", tp.ident);
212 quote!(#dummy_name)
213 }
214 GenericParam::Lifetime(lt) => {
215 let lt = <.lifetime;
216 quote!(#lt)
217 }
218 GenericParam::Const(c) => {
219 let ident = &c.ident;
220 quote!(#ident)
221 }
222 })
223 .collect();
224
225 let bound_part = self.opaque_bound
226 .map(|b| quote! { format!(": {}", #b) })
227 .unwrap_or_else(|| quote! { String::new() });
228
229 quote! {
230 fn decl(cfg: &#crate_rename::Config) -> String {
231 #(#dummy_decls)*
232 let inline = <#rust_ty<#(#full_generic_args),*> as #crate_rename::Flow>::inline(cfg);
233 let generics = format!("<{}>", vec![#(#generics_str.to_owned()),*].join(", "));
234 let bound = #bound_part;
235 format!("opaque type {}{generics}{bound} = {inline};", #flow_name)
236 }
237 }
238 } else {
239 let bound_part = self.opaque_bound
240 .map(|b| quote! { format!(": {}", #b) })
241 .unwrap_or_else(|| quote! { String::new() });
242
243 quote! {
244 fn decl(cfg: &#crate_rename::Config) -> String {
245 let inline = Self::inline(cfg);
246 let bound = #bound_part;
247 format!("opaque type {}{bound} = {inline};", #flow_name)
248 }
249 }
250 }
251 } else {
252 let has_generics = generics.type_params().next().is_some();
253 if has_generics {
254 let dummy_decls: Vec<_> = generics
259 .type_params()
260 .filter(|tp| !self.concrete.contains_key(&tp.ident))
261 .map(|tp| {
262 let ident = &tp.ident;
263 let dummy_name = format_ident!("{}Dummy", ident);
264 quote! {
265 struct #dummy_name;
266 impl #crate_rename::Flow for #dummy_name {
267 type WithoutGenerics = Self;
268 type OptionInnerType = Self;
269 fn name(_: &#crate_rename::Config) -> String {
270 stringify!(#ident).to_owned()
271 }
272 fn inline(cfg: &#crate_rename::Config) -> String {
273 Self::name(cfg)
274 }
275 }
276 }
277 })
278 .collect();
279
280 let generics_str: Vec<_> = generics
282 .type_params()
283 .filter(|tp| !self.concrete.contains_key(&tp.ident))
284 .map(|tp| {
285 let ident = &tp.ident;
286 quote!(stringify!(#ident))
287 })
288 .collect();
289
290 let full_generic_args: Vec<_> = generics
295 .params
296 .iter()
297 .map(|p| match p {
298 GenericParam::Type(tp) => {
299 if let Some(concrete_ty) = self.concrete.get(&tp.ident) {
300 quote!(#concrete_ty)
301 } else {
302 let dummy_name = format_ident!("{}Dummy", tp.ident);
303 quote!(#dummy_name)
304 }
305 }
306 GenericParam::Lifetime(lt) => {
307 let lt = <.lifetime;
308 quote!(#lt)
309 }
310 GenericParam::Const(c) => {
311 let ident = &c.ident;
312 quote!(#ident)
313 }
314 })
315 .collect();
316
317 let decl_format = if generics_str.is_empty() {
319 quote! {
320 format!("type {} = {inline};", #flow_name)
321 }
322 } else {
323 quote! {
324 let generics = format!("<{}>", vec![#(#generics_str.to_owned()),*].join(", "));
325 format!("type {}{generics} = {inline};", #flow_name)
326 }
327 };
328
329 quote! {
330 fn decl(cfg: &#crate_rename::Config) -> String {
331 #(#dummy_decls)*
335 let inline = <#rust_ty<#(#full_generic_args),*> as #crate_rename::Flow>::inline(cfg);
336 #decl_format
337 }
338 }
339 } else {
340 quote! {
341 fn decl(cfg: &#crate_rename::Config) -> String {
342 format!("type {} = {};", Self::name(cfg), Self::inline(cfg))
343 }
344 }
345 }
346 };
347
348 let decl_concrete_fn = if self.is_opaque || self.enum_decl_override.is_some() {
350 quote! {
351 fn decl_concrete(cfg: &#crate_rename::Config) -> String {
352 Self::decl(cfg)
353 }
354 }
355 } else {
356 quote! {
357 fn decl_concrete(cfg: &#crate_rename::Config) -> String {
358 format!("type {} = {};", Self::name(cfg), Self::inline(cfg))
359 }
360 }
361 };
362
363 let mut bounds = generics.clone();
365 if let Some(extra) = &self.bound {
366 let where_clause = bounds.make_where_clause();
367 for pred in extra {
368 where_clause.predicates.push(pred.clone());
369 }
370 }
371 for param in &generics.params {
373 if let GenericParam::Type(tp) = param {
374 if self.concrete.contains_key(&tp.ident) {
375 continue;
376 }
377 let ident = &tp.ident;
378 let where_clause = bounds.make_where_clause();
379 where_clause
380 .predicates
381 .push(parse_quote!(#ident: #crate_rename::Flow));
382 }
383 }
384 let (impl_generics, ty_generics, where_clause) = bounds.split_for_impl();
385
386 let without_generics = if generics.params.is_empty() {
389 quote!(Self)
390 } else {
391 let params = generics.params.iter().map(|p| match p {
392 GenericParam::Type(tp) => {
393 if let Some(concrete_ty) = self.concrete.get(&tp.ident) {
394 quote!(#concrete_ty)
395 } else {
396 quote!(#crate_rename::Dummy)
397 }
398 }
399 GenericParam::Lifetime(lt) => {
400 let lt = <.lifetime;
401 quote!(#lt)
402 }
403 GenericParam::Const(c) => {
404 let ident = &c.ident;
405 quote!(#ident)
406 }
407 });
408 quote!(#rust_ty<#(#params),*>)
409 };
410
411 let dep_tokens = self.deps.to_tokens();
413 let visit_deps_fn = quote! {
414 fn visit_dependencies(v: &mut impl #crate_rename::TypeVisitor)
415 where
416 Self: 'static,
417 {
418 #dep_tokens
419 }
420 };
421
422 let visit_generics_fn = {
424 let generic_visits: Vec<_> = generics
425 .type_params()
426 .filter(|tp| !self.concrete.contains_key(&tp.ident))
427 .map(|tp| {
428 let ident = &tp.ident;
429 quote! {
430 v.visit::<#ident>();
431 <#ident as #crate_rename::Flow>::visit_generics(v);
432 }
433 })
434 .collect();
435
436 quote! {
437 fn visit_generics(v: &mut impl #crate_rename::TypeVisitor)
438 where
439 Self: 'static,
440 {
441 #(#generic_visits)*
442 }
443 }
444 };
445
446 let inline_flattened_fn = quote! {
448 fn inline_flattened(cfg: &#crate_rename::Config) -> String {
449 #inline_flattened
450 }
451 };
452
453 quote! {
454 #[automatically_derived]
455 impl #impl_generics #crate_rename::Flow for #rust_ty #ty_generics #where_clause {
456 type WithoutGenerics = #without_generics;
457 type OptionInnerType = Self;
458
459 #name_fn
460
461 fn inline(cfg: &#crate_rename::Config) -> String {
462 #inline
463 }
464
465 #inline_flattened_fn
466 #decl_fn
467 #decl_concrete_fn
468 #docs_fn
469 #output_path_fn
470 #visit_deps_fn
471 #visit_generics_fn
472
473 const IS_ENUM: bool = #is_enum;
474 }
475
476 #export_test
477 }
478 }
479
480 fn generate_export_test(&self, rust_ty: &Ident, generics: &Generics) -> TokenStream {
481 let crate_rename = &self.crate_rename;
482 let test_name = format_ident!("export_flow_bindings_{}", rust_ty);
483 let ty = if generics.params.is_empty() {
484 quote!(#rust_ty)
485 } else {
486 let dummies = generics.params.iter().map(|p| match p {
487 GenericParam::Type(tp) => {
488 if let Some(concrete_ty) = self.concrete.get(&tp.ident) {
489 quote!(#concrete_ty)
490 } else {
491 quote!(#crate_rename::Dummy)
492 }
493 }
494 GenericParam::Lifetime(lt) => {
495 let lt = <.lifetime;
496 quote!(#lt)
497 }
498 GenericParam::Const(c) => {
499 let ident = &c.ident;
500 quote!(#ident)
501 }
502 });
503 quote!(#rust_ty<#(#dummies),*>)
504 };
505
506 quote! {
507 #[cfg(test)]
508 #[test]
509 #[allow(non_snake_case)]
510 fn #test_name() {
511 let cfg = #crate_rename::Config::from_env();
512 <#ty as #crate_rename::Flow>::export_all(&cfg)
513 .expect("could not export type");
514 }
515 }
516 }
517}
518
519#[proc_macro_derive(Flow, attributes(flow))]
541pub fn derive_flow(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
542 let input = parse_macro_input!(input as DeriveInput);
543 match derive_flow_impl(input) {
544 Ok(tokens) => tokens.into(),
545 Err(err) => err.to_compile_error().into(),
546 }
547}
548
549fn derive_flow_impl(input: DeriveInput) -> Result<TokenStream> {
550 let container = ContainerAttr::from_attrs(&input.attrs)?;
551 let ident = &input.ident;
552
553 let crate_rename = container
554 .crate_rename
555 .clone()
556 .unwrap_or_else(|| parse_quote!(::flowjs_rs));
557
558 let flow_name: Expr = match &container.rename {
559 Some(name) => parse_quote!(#name),
560 None => {
561 let name = ident.to_string();
562 parse_quote!(#name)
563 }
564 };
565
566 let docs: Vec<Expr> = input
567 .attrs
568 .iter()
569 .filter_map(|attr| {
570 if !attr.path().is_ident("doc") {
571 return None;
572 }
573 if let Meta::NameValue(nv) = &attr.meta {
574 if let Expr::Lit(lit) = &nv.value {
575 if let Lit::Str(s) = &lit.lit {
576 let val = s.value();
577 let trimmed = val.trim();
578 return Some(parse_quote!(#trimmed));
579 }
580 }
581 }
582 None
583 })
584 .collect();
585
586 let mut deps = Dependencies::new(crate_rename.clone());
587
588 if let Some(type_str) = &container.type_override {
590 let inline = quote!(#type_str.to_owned());
591 let derived = DerivedFlow {
592 crate_rename: crate_rename.clone(),
593 flow_name,
594 docs,
595 inline: inline.clone(),
596 inline_flattened: quote!(format!("({})", #type_str)),
597 is_enum: quote!(false),
598 is_opaque: false,
599 opaque_bound: None,
600 enum_decl_override: None,
601 export: container.export,
602 export_to: container.export_to.clone(),
603 bound: container.bound.clone(),
604 deps,
605 concrete: container.concrete.clone(),
606 };
607 return Ok(derived.into_impl(ident.clone(), input.generics.clone()));
608 }
609
610 if let Some(as_ty) = &container.type_as {
611 let cr = &crate_rename;
612 let inline = quote!(<#as_ty as #cr::Flow>::inline(cfg));
613 deps.push(&syn::parse_quote!(#as_ty));
614 let derived = DerivedFlow {
615 crate_rename: crate_rename.clone(),
616 flow_name,
617 docs,
618 inline: inline.clone(),
619 inline_flattened: quote!(format!("({})", <#as_ty as #cr::Flow>::inline(cfg))),
620 is_enum: quote!(false),
621 is_opaque: false,
622 opaque_bound: None,
623 enum_decl_override: None,
624 export: container.export,
625 export_to: container.export_to.clone(),
626 bound: container.bound.clone(),
627 deps,
628 concrete: container.concrete.clone(),
629 };
630 return Ok(derived.into_impl(ident.clone(), input.generics.clone()));
631 }
632
633 let (inline, inline_flattened, is_enum, enum_decl_override) = match &input.data {
634 Data::Struct(data) => {
635 let (inline, flattened) =
636 derive_struct(&crate_rename, &container, &data.fields, &mut deps)?;
637 (inline, flattened, quote!(false), None)
638 }
639 Data::Enum(data) => {
640 if let Some(repr) = &container.flow_enum {
641 if input.generics.type_params().next().is_some() {
643 return Err(syn::Error::new(
644 ident.span(),
645 "#[flow(flow_enum)] cannot be used on generic enums",
646 ));
647 }
648 if container.tag.is_some() {
650 return Err(syn::Error::new(
651 ident.span(),
652 "#[flow(flow_enum)] cannot be combined with #[flow(tag = \"...\")]",
653 ));
654 }
655 if container.content.is_some() {
656 return Err(syn::Error::new(
657 ident.span(),
658 "#[flow(flow_enum)] cannot be combined with #[flow(content = \"...\")]",
659 ));
660 }
661 if container.untagged {
662 return Err(syn::Error::new(
663 ident.span(),
664 "#[flow(flow_enum)] cannot be combined with #[flow(untagged)]",
665 ));
666 }
667 if container.opaque.is_some() {
668 return Err(syn::Error::new(
669 ident.span(),
670 "#[flow(flow_enum)] cannot be combined with #[flow(opaque)]",
671 ));
672 }
673 let (inline, flattened, enum_decl) =
674 derive_flow_enum(&container, data, repr)?;
675 (inline, flattened, quote!(true), Some(enum_decl))
676 } else {
677 let inline = derive_enum(&crate_rename, &container, data, &mut deps)?;
678 let flattened = quote! {
679 format!("({})", Self::inline(cfg))
680 };
681 (inline, flattened, quote!(true), None)
682 }
683 }
684 Data::Union(_) => {
685 return Err(syn::Error::new(
686 ident.span(),
687 "Flow cannot be derived for unions",
688 ));
689 }
690 };
691
692 let (is_opaque, opaque_bound) = match &container.opaque {
693 Some(Some(bound)) => (true, Some(quote!(#bound))),
694 Some(None) => (true, None),
695 None => {
696 let is_newtype = matches!(&input.data, Data::Struct(s) if matches!(&s.fields, Fields::Unnamed(u) if u.unnamed.len() == 1));
700 if is_newtype && config::project_config().opaque_newtypes {
701 if let Data::Struct(s) = &input.data {
703 if let Fields::Unnamed(u) = &s.fields {
704 let inner_ty = &u.unnamed[0].ty;
705 let bound_str = quote!(<#inner_ty as #crate_rename::Flow>::name(cfg));
706 (true, Some(bound_str))
707 } else {
708 (false, None)
709 }
710 } else {
711 (false, None)
712 }
713 } else {
714 (false, None)
715 }
716 }
717 };
718
719 let derived = DerivedFlow {
720 crate_rename,
721 flow_name,
722 docs,
723 inline,
724 inline_flattened,
725 is_enum,
726 is_opaque,
727 opaque_bound,
728 enum_decl_override,
729 export: container.export,
730 export_to: container.export_to.clone(),
731 bound: container.bound.clone(),
732 deps,
733 concrete: container.concrete.clone(),
734 };
735
736 Ok(derived.into_impl(ident.clone(), input.generics.clone()))
737}
738
739fn derive_struct(
740 crate_rename: &Path,
741 container: &ContainerAttr,
742 fields: &Fields,
743 deps: &mut Dependencies,
744) -> Result<(TokenStream, TokenStream)> {
745 match fields {
746 Fields::Named(named) => {
747 let mut formatted_fields: Vec<TokenStream> = Vec::new();
748 let mut flattened_fields: Vec<TokenStream> = Vec::new();
749
750 for f in &named.named {
751 let field_attr = FieldAttr::from_attrs(&f.attrs)?;
752 if field_attr.skip {
753 continue;
754 }
755
756 let field_name = f.ident.as_ref().unwrap();
757 let ty = &f.ty;
758
759 if field_attr.flatten {
760 if field_attr.type_override.is_none() {
762 deps.append_from(ty);
763 }
764 flattened_fields
765 .push(quote!(<#ty as #crate_rename::Flow>::inline_flattened(cfg)));
766 continue;
767 }
768
769 let name =
770 utils::quote_property_name(&field_attr.rename.clone().unwrap_or_else(|| {
771 let raw = field_name.to_string();
772 container.rename_field(&raw)
773 }));
774
775 let effective_ty = field_attr.type_as.as_ref().unwrap_or(ty);
778
779 if field_attr.type_override.is_none() {
781 if field_attr.inline {
782 deps.append_from(effective_ty);
783 } else {
784 deps.push(effective_ty);
785 }
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!(<#effective_ty as #crate_rename::Flow>::inline(cfg))
792 } else {
793 quote!(<#effective_ty as #crate_rename::Flow>::name(cfg))
794 };
795
796 let is_omittable = field_attr.is_optional(&container.optional_fields);
800 let opt_marker = if is_omittable { "?" } else { "" };
801 let variance = if field_attr.readonly { "+" } else { "" };
802
803 formatted_fields.push(quote! {
804 format!(" {}{}{}: {},", #variance, #name, #opt_marker, #type_str)
805 });
806 }
807
808 let inline = match (formatted_fields.len(), flattened_fields.len()) {
810 (0, 0) => quote!("{||}".to_owned()),
811 (_, 0) => quote! {{
812 let fields = vec![#(#formatted_fields),*];
813 format!("{{|\n{}\n|}}", fields.join("\n"))
814 }},
815 (0, 1) => {
816 let flat = &flattened_fields[0];
817 quote! {{
818 let f = #flat;
819 if f.starts_with('(') && f.ends_with(')') {
820 f[1..f.len() - 1].trim().to_owned()
821 } else {
822 f.trim().to_owned()
823 }
824 }}
825 }
826 (0, _) => quote! {{
827 let parts: Vec<String> = vec![#(#flattened_fields),*];
828 parts.join(" & ")
829 }},
830 (_, _) => quote! {{
831 let fields = vec![#(#formatted_fields),*];
832 let base = format!("{{|\n{}\n|}}", fields.join("\n"));
833 let flattened: Vec<String> = vec![#(#flattened_fields),*];
834 format!("{} & {}", base, flattened.join(" & "))
835 }},
836 };
837
838 let inline_flattened = match (formatted_fields.len(), flattened_fields.len()) {
840 (_, 0) => quote! {{
841 let fields = vec![#(#formatted_fields),*];
842 format!("{{|\n{}\n|}}", fields.join("\n"))
843 }},
844 (0, _) => quote! {{
845 let parts: Vec<String> = vec![#(#flattened_fields),*];
846 parts.join(" & ")
847 }},
848 (_, _) => quote! {{
849 let fields = vec![#(#formatted_fields),*];
850 let base = format!("{{|\n{}\n|}}", fields.join("\n"));
851 let flattened: Vec<String> = vec![#(#flattened_fields),*];
852 format!("{} & {}", base, flattened.join(" & "))
853 }},
854 };
855
856 Ok((inline, inline_flattened))
857 }
858 Fields::Unnamed(unnamed) => {
859 if unnamed.unnamed.len() == 1 {
860 let ty = &unnamed.unnamed[0].ty;
862 deps.push(ty);
863 let inline = quote!(<#ty as #crate_rename::Flow>::inline(cfg));
864 let flattened = quote! {
865 format!("({})", <#ty as #crate_rename::Flow>::inline(cfg))
866 };
867 Ok((inline, flattened))
868 } else {
869 let elems: Vec<TokenStream> = unnamed
871 .unnamed
872 .iter()
873 .map(|f| {
874 let ty = &f.ty;
875 deps.push(ty);
876 quote!(<#ty as #crate_rename::Flow>::inline(cfg))
877 })
878 .collect();
879 let inline = quote! {{
880 let elems: Vec<String> = vec![#(#elems),*];
881 format!("[{}]", elems.join(", "))
882 }};
883 let flattened = quote! {
884 format!("({})", Self::inline(cfg))
885 };
886 Ok((inline, flattened))
887 }
888 }
889 Fields::Unit => {
890 let inline = quote!(#crate_rename::flow_type::NULL.to_owned());
891 let flattened = quote!(#crate_rename::flow_type::NULL.to_owned());
892 Ok((inline, flattened))
893 }
894 }
895}
896
897fn derive_enum(
898 crate_rename: &Path,
899 container: &ContainerAttr,
900 data: &syn::DataEnum,
901 deps: &mut Dependencies,
902) -> Result<TokenStream> {
903 if data.variants.is_empty() {
904 return Ok(quote!(#crate_rename::flow_type::EMPTY.to_owned()));
905 }
906
907 let is_untagged = container.untagged;
908 let tag = &container.tag.as_deref().map(utils::quote_property_name);
909 let content = &container.content.as_deref().map(utils::quote_property_name);
910
911 let mut variant_defs: Vec<TokenStream> = Vec::new();
912 for v in &data.variants {
913 let variant_attr = VariantAttr::from_attrs(&v.attrs)?;
914 if variant_attr.skip {
915 continue;
916 }
917
918 let variant_name_raw = variant_attr.rename.clone().unwrap_or_else(|| {
919 let raw = v.ident.to_string();
920 container.rename_variant(&raw)
921 });
922 let variant_name = utils::escape_string_literal(&variant_name_raw);
923 let variant_key = utils::quote_property_name(&variant_name_raw);
924
925 let is_variant_untagged = is_untagged || variant_attr.untagged;
927
928 if let Some(ref type_override) = variant_attr.type_override {
930 let type_str = quote!(#type_override.to_owned());
931 let def = if is_variant_untagged {
932 type_str
933 } else if let Some(tag_field) = tag {
934 quote!(format!(
935 "{{| {}: '{}' |}} & {}",
936 #tag_field, #variant_name, #type_str
937 ))
938 } else {
939 quote!(format!("{{| {}: {} |}}", #variant_key, #type_str))
940 };
941 variant_defs.push(def);
942 continue;
943 }
944 if let Some(ref type_as) = variant_attr.type_as {
945 deps.push(&syn::parse_quote!(#type_as));
946 let type_str = quote!(<#type_as as #crate_rename::Flow>::inline(cfg));
947 let def = if is_variant_untagged {
948 type_str
949 } else if let Some(tag_field) = tag {
950 quote!(format!(
951 "{{| {}: '{}' |}} & {}",
952 #tag_field, #variant_name, #type_str
953 ))
954 } else {
955 quote!(format!("{{| {}: {} |}}", #variant_key, #type_str))
956 };
957 variant_defs.push(def);
958 continue;
959 }
960
961 let def = match &v.fields {
962 Fields::Unit => {
963 if is_variant_untagged {
964 quote!(#crate_rename::flow_type::NULL.to_owned())
965 } else if let Some(tag_field) = tag {
966 quote!(format!("{{| {}: '{}' |}}", #tag_field, #variant_name))
967 } else {
968 quote!(format!("'{}'", #variant_name))
969 }
970 }
971 Fields::Unnamed(unnamed) => {
972 if unnamed.unnamed.len() == 1 {
973 let ty = &unnamed.unnamed[0].ty;
974 deps.push(ty);
975 let inner = quote!(<#ty as #crate_rename::Flow>::inline(cfg));
976 if is_variant_untagged {
977 inner
978 } else if let (Some(tag_field), Some(content_field)) = (tag, content) {
979 quote!(format!(
980 "{{| {}: '{}', {}: {} |}}",
981 #tag_field, #variant_name, #content_field, #inner
982 ))
983 } else if let Some(tag_field) = tag {
984 quote!(format!(
985 "{{| {}: '{}' |}} & {}",
986 #tag_field, #variant_name, #inner
987 ))
988 } else {
989 quote!(format!(
990 "{{| {}: {} |}}",
991 #variant_key, #inner
992 ))
993 }
994 } else {
995 let elems: Vec<TokenStream> = unnamed
997 .unnamed
998 .iter()
999 .map(|f| {
1000 let ty = &f.ty;
1001 deps.push(ty);
1002 quote!(<#ty as #crate_rename::Flow>::inline(cfg))
1003 })
1004 .collect();
1005 let tuple = quote! {{
1006 let elems: Vec<String> = vec![#(#elems),*];
1007 format!("[{}]", elems.join(", "))
1008 }};
1009 if is_variant_untagged {
1010 tuple
1011 } else if let (Some(tag_field), Some(content_field)) = (tag, content) {
1012 quote!(format!(
1013 "{{| {}: '{}', {}: {} |}}",
1014 #tag_field, #variant_name, #content_field, #tuple
1015 ))
1016 } else {
1017 quote!(format!(
1018 "{{| {}: {} |}}",
1019 #variant_key, #tuple
1020 ))
1021 }
1022 }
1023 }
1024 Fields::Named(named) => {
1025 let mut field_defs: Vec<TokenStream> = Vec::new();
1026 let mut flattened_defs: Vec<TokenStream> = Vec::new();
1027 for f in &named.named {
1028 let field_attr = FieldAttr::from_attrs(&f.attrs)?;
1029 if field_attr.skip {
1030 continue;
1031 }
1032 let ty = &f.ty;
1033
1034 if field_attr.flatten {
1035 if field_attr.type_override.is_none() {
1036 deps.append_from(ty);
1037 }
1038 flattened_defs
1039 .push(quote!(<#ty as #crate_rename::Flow>::inline_flattened(cfg)));
1040 continue;
1041 }
1042
1043 let field_name = f.ident.as_ref().unwrap();
1044 let name =
1046 utils::quote_property_name(&field_attr.rename.clone().unwrap_or_else(
1047 || {
1048 let raw = field_name.to_string();
1049 if let Some(ref inflection) = variant_attr.rename_all {
1050 inflection.apply(&raw)
1051 } else {
1052 container.rename_variant_field(&raw)
1053 }
1054 },
1055 ));
1056 let effective_inline = field_attr.inline || variant_attr.inline;
1058 if field_attr.type_override.is_none() {
1059 if effective_inline {
1060 deps.append_from(ty);
1061 } else {
1062 deps.push(ty);
1063 }
1064 }
1065 let effective_ty = field_attr.type_as.as_ref().unwrap_or(ty);
1066 let type_str = if let Some(override_ty) = &field_attr.type_override {
1067 quote!(#override_ty.to_owned())
1068 } else if effective_inline {
1069 quote!(<#effective_ty as #crate_rename::Flow>::inline(cfg))
1070 } else {
1071 quote!(<#effective_ty as #crate_rename::Flow>::name(cfg))
1072 };
1073 let variant_optional = match &variant_attr.optional_fields {
1075 attr::Optional::Inherit => &container.optional_fields,
1076 explicit => explicit,
1077 };
1078 let is_omittable = field_attr.is_optional(variant_optional);
1079 let opt_marker = if is_omittable { "?" } else { "" };
1080 let variance = if field_attr.readonly { "+" } else { "" };
1081 field_defs.push(quote!(format!("{}{}{}: {}", #variance, #name, #opt_marker, #type_str)));
1082 }
1083
1084 let obj = if flattened_defs.is_empty() {
1085 quote! {{
1086 let fields: Vec<String> = vec![#(#field_defs),*];
1087 format!("{{| {} |}}", fields.join(", "))
1088 }}
1089 } else if field_defs.is_empty() {
1090 quote! {{
1091 let parts: Vec<String> = vec![#(#flattened_defs),*];
1092 parts.join(" & ")
1093 }}
1094 } else {
1095 quote! {{
1096 let fields: Vec<String> = vec![#(#field_defs),*];
1097 let base = format!("{{| {} |}}", fields.join(", "));
1098 let flattened: Vec<String> = vec![#(#flattened_defs),*];
1099 format!("{} & {}", base, flattened.join(" & "))
1100 }}
1101 };
1102
1103 if is_variant_untagged {
1104 obj
1105 } else if let (Some(tag_field), Some(content_field)) = (tag, content) {
1106 quote!(format!(
1107 "{{| {}: '{}', {}: {} |}}",
1108 #tag_field, #variant_name, #content_field, #obj
1109 ))
1110 } else if let Some(tag_field) = tag {
1111 let tag_field_def = quote!(format!("{}: '{}'", #tag_field, #variant_name));
1114
1115 if flattened_defs.is_empty() {
1116 let all_fields: Vec<_> = std::iter::once(tag_field_def.clone())
1118 .chain(field_defs.iter().cloned())
1119 .collect();
1120 quote! {{
1121 let fields: Vec<String> = vec![#(#all_fields),*];
1122 format!("{{| {} |}}", fields.join(", "))
1123 }}
1124 } else {
1125 let base_fields: Vec<_> = std::iter::once(tag_field_def.clone())
1127 .chain(field_defs.iter().cloned())
1128 .collect();
1129 quote! {{
1130 let fields: Vec<String> = vec![#(#base_fields),*];
1131 let base = format!("{{| {} |}}", fields.join(", "));
1132 let flattened: Vec<String> = vec![#(#flattened_defs),*];
1133 format!("{} & {}", base, flattened.join(" & "))
1134 }}
1135 }
1136 } else {
1137 quote!(format!(
1138 "{{| {}: {} |}}",
1139 #variant_key, #obj
1140 ))
1141 }
1142 }
1143 };
1144 variant_defs.push(def);
1145 }
1146
1147 if variant_defs.is_empty() {
1148 return Ok(quote!(#crate_rename::flow_type::EMPTY.to_owned()));
1149 }
1150
1151 Ok(quote! {{
1152 let variants: Vec<String> = vec![#(#variant_defs),*];
1153 variants.join(" | ")
1154 }})
1155}
1156
1157fn derive_flow_enum(
1165 container: &ContainerAttr,
1166 data: &syn::DataEnum,
1167 repr: &FlowEnumRepr,
1168) -> Result<(TokenStream, TokenStream, TokenStream)> {
1169 for v in &data.variants {
1171 if !matches!(v.fields, Fields::Unit) {
1172 return Err(syn::Error::new(
1173 v.ident.span(),
1174 "#[flow(flow_enum)] requires all variants to be unit (no fields)",
1175 ));
1176 }
1177 }
1178
1179 if matches!(repr, FlowEnumRepr::Boolean) {
1181 let active_count = data
1182 .variants
1183 .iter()
1184 .filter(|v| !VariantAttr::from_attrs(&v.attrs).map(|a| a.skip).unwrap_or(false))
1185 .count();
1186 if active_count != 2 {
1187 return Err(syn::Error::new(
1188 data.variants.first().map(|v| v.ident.span()).unwrap_or_else(proc_macro2::Span::call_site),
1189 "#[flow(flow_enum = \"boolean\")] requires exactly 2 non-skipped variants",
1190 ));
1191 }
1192 }
1193
1194 let mut member_defs: Vec<TokenStream> = Vec::new();
1195 let mut next_discriminant: i64 = 0;
1196 let mut bool_index: usize = 0;
1197
1198 for v in &data.variants {
1199 let variant_attr = VariantAttr::from_attrs(&v.attrs)?;
1200 if variant_attr.skip {
1201 continue;
1202 }
1203
1204 let member_name = v.ident.to_string();
1205
1206 match repr {
1207 FlowEnumRepr::Symbol => {
1208 member_defs.push(quote!(format!(" {},", #member_name)));
1209 }
1210 FlowEnumRepr::String => {
1211 let value = variant_attr.rename.clone().unwrap_or_else(|| {
1212 container.rename_variant(&member_name)
1213 });
1214 let escaped = utils::escape_string_literal(&value);
1215 member_defs.push(quote!(format!(" {} = '{}',", #member_name, #escaped)));
1216 }
1217 FlowEnumRepr::Number => {
1218 let disc_value = if let Some((_, expr)) = &v.discriminant {
1220 match expr {
1221 Expr::Lit(lit) => match &lit.lit {
1222 Lit::Int(i) => {
1223 let val = i.base10_parse::<i64>().map_err(|e| {
1224 syn::Error::new(i.span(), format!("invalid discriminant: {e}"))
1225 })?;
1226 next_discriminant = val + 1;
1227 val
1228 }
1229 _ => {
1230 return Err(syn::Error::new(
1231 expr.span(),
1232 "flow_enum = \"number\" requires integer literal discriminants",
1233 ));
1234 }
1235 },
1236 Expr::Unary(unary) if matches!(unary.op, syn::UnOp::Neg(_)) => {
1237 if let Expr::Lit(lit) = &*unary.expr {
1238 if let Lit::Int(i) = &lit.lit {
1239 let val = -(i.base10_parse::<i64>().map_err(|e| {
1240 syn::Error::new(i.span(), format!("invalid discriminant: {e}"))
1241 })?);
1242 next_discriminant = val + 1;
1243 val
1244 } else {
1245 return Err(syn::Error::new(
1246 expr.span(),
1247 "flow_enum = \"number\" requires integer literal discriminants",
1248 ));
1249 }
1250 } else {
1251 return Err(syn::Error::new(
1252 expr.span(),
1253 "flow_enum = \"number\" requires integer literal discriminants",
1254 ));
1255 }
1256 }
1257 _ => {
1258 return Err(syn::Error::new(
1259 expr.span(),
1260 "flow_enum = \"number\" requires integer literal discriminants",
1261 ));
1262 }
1263 }
1264 } else {
1265 let val = next_discriminant;
1266 next_discriminant = val + 1;
1267 val
1268 };
1269 member_defs.push(quote!(format!(" {} = {},", #member_name, #disc_value)));
1270 }
1271 FlowEnumRepr::Boolean => {
1272 let bool_val = if bool_index == 0 { "true" } else { "false" };
1273 bool_index += 1;
1274 member_defs.push(quote!(format!(" {} = {},", #member_name, #bool_val)));
1275 }
1276 }
1277 }
1278
1279 if member_defs.is_empty() {
1281 let inline = quote!("empty".to_owned());
1282 let inline_flattened = quote!("empty".to_owned());
1283 let enum_decl = quote! {
1284 format!("type {} = empty;", Self::ident(cfg))
1285 };
1286 return Ok((inline, inline_flattened, enum_decl));
1287 }
1288
1289 let repr_suffix = match repr {
1290 FlowEnumRepr::Symbol => "",
1291 FlowEnumRepr::String => " of string",
1292 FlowEnumRepr::Number => " of number",
1293 FlowEnumRepr::Boolean => " of boolean",
1294 };
1295
1296 let enum_decl = quote! {{
1297 let members: Vec<String> = vec![#(#member_defs),*];
1298 format!("enum {}{} {{\n{}\n}}", Self::ident(cfg), #repr_suffix, members.join("\n"))
1299 }};
1300
1301 let inline = quote!(Self::name(cfg));
1303 let inline_flattened = quote! {
1304 format!("({})", Self::name(cfg))
1305 };
1306
1307 Ok((inline, inline_flattened, enum_decl))
1308}