1use proc_macro::TokenStream;
7use proc_macro2::TokenStream as TokenStream2;
8use quote::quote;
9use syn::{
10 parse_macro_input, Attribute, Data, DeriveInput, Fields, GenericParam, Generics, Ident, Type,
11};
12
13#[derive(Debug, Clone, Copy, PartialEq)]
19enum RenameAll {
20 CamelCase,
22 PascalCase,
24 SnakeCase,
26 ScreamingSnakeCase,
28 KebabCase,
30 ScreamingKebabCase,
32}
33
34impl RenameAll {
35 fn from_str(s: &str) -> Option<Self> {
36 match s {
37 "camelCase" => Some(RenameAll::CamelCase),
38 "PascalCase" => Some(RenameAll::PascalCase),
39 "snake_case" => Some(RenameAll::SnakeCase),
40 "SCREAMING_SNAKE_CASE" => Some(RenameAll::ScreamingSnakeCase),
41 "kebab-case" => Some(RenameAll::KebabCase),
42 "SCREAMING-KEBAB-CASE" => Some(RenameAll::ScreamingKebabCase),
43 _ => None,
44 }
45 }
46
47 fn apply(&self, name: &str) -> String {
48 match self {
49 RenameAll::CamelCase => to_camel_case(name),
50 RenameAll::PascalCase => to_pascal_case(name),
51 RenameAll::SnakeCase => to_snake_case(name),
52 RenameAll::ScreamingSnakeCase => to_snake_case(name).to_uppercase(),
53 RenameAll::KebabCase => to_snake_case(name).replace('_', "-"),
54 RenameAll::ScreamingKebabCase => to_snake_case(name).replace('_', "-").to_uppercase(),
55 }
56 }
57}
58
59fn to_camel_case(name: &str) -> String {
61 let mut result = String::new();
62 let mut capitalize_next = false;
63
64 for (i, c) in name.chars().enumerate() {
65 if c == '_' {
66 capitalize_next = true;
67 } else if capitalize_next {
68 result.push(c.to_ascii_uppercase());
69 capitalize_next = false;
70 } else if i == 0 {
71 result.push(c.to_ascii_lowercase());
72 } else {
73 result.push(c);
74 }
75 }
76
77 result
78}
79
80fn to_pascal_case(name: &str) -> String {
82 let mut result = String::new();
83 let mut capitalize_next = true;
84
85 for c in name.chars() {
86 if c == '_' {
87 capitalize_next = true;
88 } else if capitalize_next {
89 result.push(c.to_ascii_uppercase());
90 capitalize_next = false;
91 } else {
92 result.push(c);
93 }
94 }
95
96 result
97}
98
99fn to_snake_case(name: &str) -> String {
101 let mut result = String::new();
102
103 for (i, c) in name.chars().enumerate() {
104 if c.is_uppercase() && i > 0 {
105 result.push('_');
106 }
107 result.push(c.to_ascii_lowercase());
108 }
109
110 result
111}
112
113#[derive(Default)]
115struct ContainerAttrs {
116 rename: Option<String>,
118 rename_all: Option<RenameAll>,
120 transparent: bool,
122 tag: Option<String>,
124 content: Option<String>,
126 untagged: bool,
128 pattern: Option<String>,
130 namespace: Vec<String>,
132 extends: Option<String>,
134}
135
136impl ContainerAttrs {
137 fn from_attrs(attrs: &[Attribute]) -> syn::Result<Self> {
138 let mut result = ContainerAttrs::default();
139
140 for attr in attrs {
141 if !attr.path().is_ident("ts") {
142 continue;
143 }
144
145 attr.parse_nested_meta(|meta| {
146 if meta.path.is_ident("rename") {
147 let value: syn::LitStr = meta.value()?.parse()?;
148 result.rename = Some(value.value());
149 } else if meta.path.is_ident("rename_all") {
150 let value: syn::LitStr = meta.value()?.parse()?;
151 let s = value.value();
152 result.rename_all = RenameAll::from_str(&s);
153 if result.rename_all.is_none() {
154 return Err(syn::Error::new_spanned(
155 value,
156 format!(
157 "unknown rename_all value: '{}'. Expected one of: \
158 camelCase, PascalCase, snake_case, \
159 SCREAMING_SNAKE_CASE, kebab-case, SCREAMING-KEBAB-CASE",
160 s
161 ),
162 ));
163 }
164 } else if meta.path.is_ident("transparent") {
165 result.transparent = true;
166 } else if meta.path.is_ident("tag") {
167 let value: syn::LitStr = meta.value()?.parse()?;
168 result.tag = Some(value.value());
169 } else if meta.path.is_ident("content") {
170 let value: syn::LitStr = meta.value()?.parse()?;
171 result.content = Some(value.value());
172 } else if meta.path.is_ident("untagged") {
173 result.untagged = true;
174 } else if meta.path.is_ident("pattern") {
175 let value: syn::LitStr = meta.value()?.parse()?;
176 result.pattern = Some(value.value());
177 } else if meta.path.is_ident("namespace") {
178 let value: syn::LitStr = meta.value()?.parse()?;
179 let ns_str = value.value();
181 result.namespace = ns_str
182 .split(|c| c == ':' || c == '.')
183 .filter(|s| !s.is_empty())
184 .map(|s| s.to_string())
185 .collect();
186 } else if meta.path.is_ident("extends") {
187 let value: syn::LitStr = meta.value()?.parse()?;
188 result.extends = Some(value.value());
189 }
190 Ok(())
191 })?;
192 }
193
194 Ok(result)
195 }
196}
197
198#[derive(Default)]
200struct FieldAttrs {
201 rename: Option<String>,
203 skip: bool,
205 flatten: bool,
207 type_override: Option<String>,
209 default: bool,
211 inline: bool,
213 index: Option<String>,
215 key: Option<String>,
217 pattern: Option<String>,
219}
220
221impl FieldAttrs {
222 fn from_attrs(attrs: &[Attribute]) -> syn::Result<Self> {
223 let mut result = FieldAttrs::default();
224
225 for attr in attrs {
226 if !attr.path().is_ident("ts") {
227 continue;
228 }
229
230 attr.parse_nested_meta(|meta| {
231 if meta.path.is_ident("rename") {
232 let value: syn::LitStr = meta.value()?.parse()?;
233 result.rename = Some(value.value());
234 } else if meta.path.is_ident("skip") {
235 result.skip = true;
236 } else if meta.path.is_ident("flatten") {
237 result.flatten = true;
238 } else if meta.path.is_ident("type") {
239 let value: syn::LitStr = meta.value()?.parse()?;
240 result.type_override = Some(value.value());
241 } else if meta.path.is_ident("default") {
242 result.default = true;
243 } else if meta.path.is_ident("inline") {
244 result.inline = true;
245 } else if meta.path.is_ident("index") {
246 let value: syn::LitStr = meta.value()?.parse()?;
247 result.index = Some(value.value());
248 } else if meta.path.is_ident("key") {
249 let value: syn::LitStr = meta.value()?.parse()?;
250 result.key = Some(value.value());
251 } else if meta.path.is_ident("pattern") {
252 let value: syn::LitStr = meta.value()?.parse()?;
253 result.pattern = Some(value.value());
254 }
255 Ok(())
256 })?;
257 }
258
259 Ok(result)
260 }
261
262 fn has_indexed_access(&self) -> bool {
264 self.index.is_some() && self.key.is_some()
265 }
266
267 fn has_pattern(&self) -> bool {
269 self.pattern.is_some()
270 }
271}
272
273fn get_field_name(
275 original: &str,
276 field_attrs: &FieldAttrs,
277 container_attrs: &ContainerAttrs,
278) -> String {
279 if let Some(ref renamed) = field_attrs.rename {
281 return renamed.clone();
282 }
283
284 if let Some(rename_all) = container_attrs.rename_all {
286 return rename_all.apply(original);
287 }
288
289 original.to_string()
291}
292
293fn parse_template_pattern(pattern: &str) -> syn::Result<(Vec<String>, Vec<String>)> {
303 let mut strings = Vec::new();
304 let mut types = Vec::new();
305 let mut current = String::new();
306 let mut chars = pattern.chars().peekable();
307
308 while let Some(c) = chars.next() {
309 if c == '$' && chars.peek() == Some(&'{') {
310 strings.push(std::mem::take(&mut current));
312 chars.next(); let mut type_name = String::new();
315 let mut depth = 1;
316 while let Some(tc) = chars.next() {
317 if tc == '{' {
318 depth += 1;
319 type_name.push(tc);
320 } else if tc == '}' {
321 depth -= 1;
322 if depth == 0 {
323 break;
324 }
325 type_name.push(tc);
326 } else {
327 type_name.push(tc);
328 }
329 }
330
331 if type_name.is_empty() {
332 return Err(syn::Error::new(
333 proc_macro2::Span::call_site(),
334 "Empty type placeholder ${} in pattern",
335 ));
336 }
337 types.push(type_name);
338 } else {
339 current.push(c);
340 }
341 }
342
343 strings.push(current);
345
346 Ok((strings, types))
347}
348
349fn type_name_to_typedef(name: &str) -> TokenStream2 {
352 match name.trim() {
353 "string" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::String) },
354 "number" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Number) },
355 "boolean" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Boolean) },
356 "bigint" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::BigInt) },
357 "any" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Any) },
358 "unknown" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Unknown) },
359 other => {
361 let type_ref = other.trim();
362 quote! { ferro_type::TypeDef::Ref(#type_ref.to_string()) }
363 }
364 }
365}
366
367fn generate_template_literal_expr(strings: &[String], types: &[String]) -> TokenStream2 {
369 let string_literals: Vec<_> = strings.iter().map(|s| quote! { #s.to_string() }).collect();
370 let type_exprs: Vec<_> = types.iter().map(|t| {
371 let typedef = type_name_to_typedef(t);
372 quote! { Box::new(#typedef) }
373 }).collect();
374
375 quote! {
376 ferro_type::TypeDef::TemplateLiteral {
377 strings: vec![#(#string_literals),*],
378 types: vec![#(#type_exprs),*],
379 }
380 }
381}
382
383#[proc_macro_derive(TypeScript, attributes(ts))]
429pub fn derive_typescript(input: TokenStream) -> TokenStream {
430 let input = parse_macro_input!(input as DeriveInput);
431
432 match expand_derive_typescript(&input) {
433 Ok(tokens) => tokens.into(),
434 Err(err) => err.to_compile_error().into(),
435 }
436}
437
438fn expand_derive_typescript(input: &DeriveInput) -> syn::Result<TokenStream2> {
439 let name = &input.ident;
440 let generics = &input.generics;
441
442 let container_attrs = ContainerAttrs::from_attrs(&input.attrs)?;
444
445 let type_name = container_attrs
447 .rename
448 .clone()
449 .unwrap_or_else(|| name.to_string());
450
451 match &input.data {
452 Data::Enum(data) => {
453 let typedef = generate_enum_typedef(&data.variants, &container_attrs)?;
454 generate_impl(name, &type_name, &container_attrs.namespace, generics, typedef)
455 }
456 Data::Struct(data) => {
457 if container_attrs.transparent {
459 if let syn::Fields::Unnamed(fields) = &data.fields {
460 if fields.unnamed.len() == 1 {
461 let inner_type = &fields.unnamed.first().unwrap().ty;
462 return generate_transparent_impl(name, inner_type, generics);
463 }
464 }
465 return Err(syn::Error::new_spanned(
466 input,
467 "#[ts(transparent)] can only be used on newtype structs (single unnamed field)",
468 ));
469 }
470
471 if let Some(ref pattern) = container_attrs.pattern {
473 let (strings, types) = parse_template_pattern(pattern)?;
474 let typedef = generate_template_literal_expr(&strings, &types);
475 return generate_impl(name, &type_name, &[], generics, typedef);
476 }
477
478 let typedef = generate_struct_typedef(&data.fields, &container_attrs)?;
479
480 let typedef = if let Some(ref extends_type) = container_attrs.extends {
482 quote! {
483 ferro_type::TypeDef::Intersection(vec![
484 ferro_type::TypeDef::Ref(#extends_type.to_string()),
485 #typedef
486 ])
487 }
488 } else {
489 typedef
490 };
491
492 generate_impl(name, &type_name, &container_attrs.namespace, generics, typedef)
493 }
494 Data::Union(_) => {
495 Err(syn::Error::new_spanned(
496 input,
497 "TypeScript derive is not supported for unions",
498 ))
499 }
500 }
501}
502
503fn generate_enum_typedef(
504 variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
505 container_attrs: &ContainerAttrs,
506) -> syn::Result<TokenStream2> {
507 if variants.is_empty() {
508 return Err(syn::Error::new(
509 proc_macro2::Span::call_site(),
510 "Cannot derive TypeScript for empty enum",
511 ));
512 }
513
514 let all_unit = variants.iter().all(|v| matches!(v.fields, Fields::Unit));
516
517 if container_attrs.untagged {
519 return generate_untagged_enum(variants, container_attrs);
520 }
521
522 let tag_name = container_attrs.tag.as_deref().unwrap_or("type");
524
525 let content_name = container_attrs.content.as_deref();
527
528 if all_unit {
529 let mut variant_exprs: Vec<TokenStream2> = Vec::new();
531 for v in variants.iter() {
532 let variant_attrs = FieldAttrs::from_attrs(&v.attrs)?;
533 let name = get_field_name(&v.ident.to_string(), &variant_attrs, container_attrs);
534 variant_exprs.push(
535 quote! { ferro_type::TypeDef::Literal(ferro_type::Literal::String(#name.to_string())) }
536 );
537 }
538
539 Ok(quote! {
540 ferro_type::TypeDef::Union(vec![#(#variant_exprs),*])
541 })
542 } else {
543 let mut variant_exprs: Vec<TokenStream2> = Vec::new();
545
546 for variant in variants.iter() {
547 let variant_attrs = FieldAttrs::from_attrs(&variant.attrs)?;
548 let variant_name_str = get_field_name(
549 &variant.ident.to_string(),
550 &variant_attrs,
551 container_attrs,
552 );
553
554 let expr = match &variant.fields {
555 Fields::Unit => {
556 quote! {
558 ferro_type::TypeDef::Object(vec![
559 ferro_type::Field::new(
560 #tag_name,
561 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
562 )
563 ])
564 }
565 }
566 Fields::Unnamed(fields) => {
567 if let Some(content) = content_name {
568 let content_type = if fields.unnamed.len() == 1 {
570 let field_type = &fields.unnamed.first().unwrap().ty;
571 type_to_typedef(field_type)
572 } else {
573 let field_exprs: Vec<TokenStream2> = fields
574 .unnamed
575 .iter()
576 .map(|f| type_to_typedef(&f.ty))
577 .collect();
578 quote! { ferro_type::TypeDef::Tuple(vec![#(#field_exprs),*]) }
579 };
580 quote! {
581 ferro_type::TypeDef::Object(vec![
582 ferro_type::Field::new(
583 #tag_name,
584 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
585 ),
586 ferro_type::Field::new(#content, #content_type)
587 ])
588 }
589 } else if fields.unnamed.len() == 1 {
590 let field_type = &fields.unnamed.first().unwrap().ty;
592 let type_expr = type_to_typedef(field_type);
593 quote! {
594 ferro_type::TypeDef::Object(vec![
595 ferro_type::Field::new(
596 #tag_name,
597 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
598 ),
599 ferro_type::Field::new("value", #type_expr)
600 ])
601 }
602 } else {
603 let field_exprs: Vec<TokenStream2> = fields
605 .unnamed
606 .iter()
607 .map(|f| type_to_typedef(&f.ty))
608 .collect();
609 quote! {
610 ferro_type::TypeDef::Object(vec![
611 ferro_type::Field::new(
612 #tag_name,
613 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
614 ),
615 ferro_type::Field::new(
616 "value",
617 ferro_type::TypeDef::Tuple(vec![#(#field_exprs),*])
618 )
619 ])
620 }
621 }
622 }
623 Fields::Named(fields) => {
624 let mut field_exprs: Vec<TokenStream2> = Vec::new();
625 for f in fields.named.iter() {
626 let field_attrs = FieldAttrs::from_attrs(&f.attrs)?;
627 if field_attrs.skip {
628 continue;
629 }
630 let original_name = f.ident.as_ref().unwrap().to_string();
631 let field_name = field_attrs.rename.clone().unwrap_or(original_name);
632 let field_type = &f.ty;
633 let type_expr = type_to_typedef(field_type);
634 field_exprs.push(quote! {
635 ferro_type::Field::new(#field_name, #type_expr)
636 });
637 }
638
639 if let Some(content) = content_name {
640 quote! {
642 ferro_type::TypeDef::Object(vec![
643 ferro_type::Field::new(
644 #tag_name,
645 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
646 ),
647 ferro_type::Field::new(
648 #content,
649 ferro_type::TypeDef::Object(vec![#(#field_exprs),*])
650 )
651 ])
652 }
653 } else {
654 quote! {
656 ferro_type::TypeDef::Object({
657 let mut fields = vec![
658 ferro_type::Field::new(
659 #tag_name,
660 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
661 )
662 ];
663 fields.extend(vec![#(#field_exprs),*]);
664 fields
665 })
666 }
667 }
668 }
669 };
670 variant_exprs.push(expr);
671 }
672
673 Ok(quote! {
674 ferro_type::TypeDef::Union(vec![#(#variant_exprs),*])
675 })
676 }
677}
678
679fn generate_untagged_enum(
681 variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
682 container_attrs: &ContainerAttrs,
683) -> syn::Result<TokenStream2> {
684 let mut variant_exprs: Vec<TokenStream2> = Vec::new();
685
686 for variant in variants.iter() {
687 let variant_attrs = FieldAttrs::from_attrs(&variant.attrs)?;
688 let variant_name_str = get_field_name(
689 &variant.ident.to_string(),
690 &variant_attrs,
691 container_attrs,
692 );
693
694 let expr = match &variant.fields {
695 Fields::Unit => {
696 quote! {
698 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
699 }
700 }
701 Fields::Unnamed(fields) => {
702 if fields.unnamed.len() == 1 {
703 let field_type = &fields.unnamed.first().unwrap().ty;
705 type_to_typedef(field_type)
706 } else {
707 let field_exprs: Vec<TokenStream2> = fields
709 .unnamed
710 .iter()
711 .map(|f| type_to_typedef(&f.ty))
712 .collect();
713 quote! {
714 ferro_type::TypeDef::Tuple(vec![#(#field_exprs),*])
715 }
716 }
717 }
718 Fields::Named(fields) => {
719 let mut field_exprs: Vec<TokenStream2> = Vec::new();
721 for f in fields.named.iter() {
722 let field_attrs = FieldAttrs::from_attrs(&f.attrs)?;
723 if field_attrs.skip {
724 continue;
725 }
726 let original_name = f.ident.as_ref().unwrap().to_string();
727 let field_name = field_attrs.rename.clone().unwrap_or(original_name);
728 let field_type = &f.ty;
729 let type_expr = type_to_typedef(field_type);
730 field_exprs.push(quote! {
731 ferro_type::Field::new(#field_name, #type_expr)
732 });
733 }
734 quote! {
735 ferro_type::TypeDef::Object(vec![#(#field_exprs),*])
736 }
737 }
738 };
739 variant_exprs.push(expr);
740 }
741
742 Ok(quote! {
743 ferro_type::TypeDef::Union(vec![#(#variant_exprs),*])
744 })
745}
746
747fn generate_struct_typedef(
748 fields: &syn::Fields,
749 container_attrs: &ContainerAttrs,
750) -> syn::Result<TokenStream2> {
751 match fields {
752 syn::Fields::Named(fields) => {
753 if fields.named.is_empty() {
755 return Ok(quote! { ferro_type::TypeDef::Object(vec![]) });
757 }
758
759 let mut regular_field_exprs: Vec<TokenStream2> = Vec::new();
761 let mut flatten_exprs: Vec<TokenStream2> = Vec::new();
762
763 for f in fields.named.iter() {
764 let field_attrs = FieldAttrs::from_attrs(&f.attrs)?;
765 if field_attrs.skip {
767 continue;
768 }
769
770 let field_type = &f.ty;
771
772 if field_attrs.index.is_some() != field_attrs.key.is_some() {
774 return Err(syn::Error::new_spanned(
775 f,
776 "#[ts(index = \"...\")] and #[ts(key = \"...\")] must be used together",
777 ));
778 }
779
780 if field_attrs.flatten {
781 flatten_exprs.push(quote! {
783 {
784 let inner_td = <#field_type as ferro_type::TypeScript>::typescript();
785 ferro_type::extract_object_fields(&inner_td)
786 }
787 });
788 } else {
789 let original_name = f.ident.as_ref().unwrap().to_string();
790 let field_name = get_field_name(&original_name, &field_attrs, container_attrs);
791
792 let type_expr = if let Some(ref type_override) = field_attrs.type_override {
794 quote! { ferro_type::TypeDef::Ref(#type_override.to_string()) }
795 } else if field_attrs.has_indexed_access() {
796 let index_base = field_attrs.index.as_ref().unwrap();
798 let index_key = field_attrs.key.as_ref().unwrap();
799 quote! {
800 ferro_type::TypeDef::IndexedAccess {
801 base: #index_base.to_string(),
802 key: #index_key.to_string(),
803 }
804 }
805 } else if field_attrs.has_pattern() {
806 let pattern = field_attrs.pattern.as_ref().unwrap();
808 let (strings, types) = parse_template_pattern(pattern)?;
809 generate_template_literal_expr(&strings, &types)
810 } else {
811 let base_expr = type_to_typedef(field_type);
812 if field_attrs.inline {
813 quote! { ferro_type::inline_typedef(#base_expr) }
814 } else {
815 base_expr
816 }
817 };
818
819 if field_attrs.default {
821 regular_field_exprs.push(quote! {
822 ferro_type::Field::optional(#field_name, #type_expr)
823 });
824 } else {
825 regular_field_exprs.push(quote! {
826 ferro_type::Field::new(#field_name, #type_expr)
827 });
828 }
829 }
830 }
831
832 if flatten_exprs.is_empty() {
834 Ok(quote! {
835 ferro_type::TypeDef::Object(vec![#(#regular_field_exprs),*])
836 })
837 } else {
838 Ok(quote! {
839 {
840 let mut fields = vec![#(#regular_field_exprs),*];
841 #(fields.extend(#flatten_exprs);)*
842 ferro_type::TypeDef::Object(fields)
843 }
844 })
845 }
846 }
847 syn::Fields::Unnamed(fields) => {
848 if fields.unnamed.len() == 1 {
850 let field_type = &fields.unnamed.first().unwrap().ty;
852 let type_expr = type_to_typedef(field_type);
853 Ok(quote! { #type_expr })
854 } else {
855 let field_exprs: Vec<TokenStream2> = fields
857 .unnamed
858 .iter()
859 .map(|f| type_to_typedef(&f.ty))
860 .collect();
861
862 Ok(quote! {
863 ferro_type::TypeDef::Tuple(vec![#(#field_exprs),*])
864 })
865 }
866 }
867 syn::Fields::Unit => {
868 Ok(quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Null) })
870 }
871 }
872}
873
874fn type_to_typedef(ty: &Type) -> TokenStream2 {
877 quote! { <#ty as ferro_type::TypeScript>::typescript() }
878}
879
880fn generate_transparent_impl(
883 name: &Ident,
884 inner_type: &Type,
885 generics: &Generics,
886) -> syn::Result<TokenStream2> {
887 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
888
889 let where_clause = if generics.params.is_empty() {
891 where_clause.cloned()
892 } else {
893 let type_params: Vec<_> = generics.params.iter().filter_map(|p| {
894 if let GenericParam::Type(tp) = p {
895 Some(&tp.ident)
896 } else {
897 None
898 }
899 }).collect();
900
901 if type_params.is_empty() {
902 where_clause.cloned()
903 } else {
904 let bounds = type_params.iter().map(|p| {
905 quote! { #p: ferro_type::TypeScript }
906 });
907
908 if let Some(existing_where) = where_clause {
909 let existing_predicates = &existing_where.predicates;
910 Some(syn::parse_quote! { where #(#bounds,)* #existing_predicates })
911 } else {
912 Some(syn::parse_quote! { where #(#bounds),* })
913 }
914 }
915 };
916
917 Ok(quote! {
918 impl #impl_generics ferro_type::TypeScript for #name #ty_generics #where_clause {
919 fn typescript() -> ferro_type::TypeDef {
920 <#inner_type as ferro_type::TypeScript>::typescript()
921 }
922 }
923 })
924}
925
926fn generate_impl(
927 name: &Ident,
928 name_str: &str,
929 namespace: &[String],
930 generics: &Generics,
931 typedef_expr: TokenStream2,
932) -> syn::Result<TokenStream2> {
933 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
934
935 let where_clause = if generics.params.is_empty() {
937 where_clause.cloned()
938 } else {
939 let type_params: Vec<_> = generics.params.iter().filter_map(|p| {
940 if let GenericParam::Type(tp) = p {
941 Some(&tp.ident)
942 } else {
943 None
944 }
945 }).collect();
946
947 if type_params.is_empty() {
948 where_clause.cloned()
949 } else {
950 let bounds = type_params.iter().map(|p| {
951 quote! { #p: ferro_type::TypeScript }
952 });
953
954 if let Some(existing_where) = where_clause {
955 let existing_predicates = &existing_where.predicates;
956 Some(syn::parse_quote! { where #(#bounds,)* #existing_predicates })
957 } else {
958 Some(syn::parse_quote! { where #(#bounds),* })
959 }
960 }
961 };
962
963 let registration = if generics.params.is_empty() {
966 let register_name = syn::Ident::new(
967 &format!("__FERRO_TYPE_REGISTER_{}", name.to_string().to_uppercase()),
968 name.span(),
969 );
970 quote! {
971 #[ferro_type::linkme::distributed_slice(ferro_type::TYPESCRIPT_TYPES)]
972 #[linkme(crate = ferro_type::linkme)]
973 static #register_name: fn() -> ferro_type::TypeDef = || <#name as ferro_type::TypeScript>::typescript();
974 }
975 } else {
976 quote! {}
977 };
978
979 let namespace_expr = if namespace.is_empty() {
981 quote! { vec![] }
982 } else {
983 let ns_strings = namespace.iter().map(|s| quote! { #s.to_string() });
984 quote! { vec![#(#ns_strings),*] }
985 };
986
987 Ok(quote! {
988 impl #impl_generics ferro_type::TypeScript for #name #ty_generics #where_clause {
989 fn typescript() -> ferro_type::TypeDef {
990 ferro_type::TypeDef::Named {
991 namespace: #namespace_expr,
992 name: #name_str.to_string(),
993 def: Box::new(#typedef_expr),
994 module: Some(module_path!().to_string()),
995 }
996 }
997 }
998
999 #registration
1000 })
1001}