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}
133
134impl ContainerAttrs {
135 fn from_attrs(attrs: &[Attribute]) -> syn::Result<Self> {
136 let mut result = ContainerAttrs::default();
137
138 for attr in attrs {
139 if !attr.path().is_ident("ts") {
140 continue;
141 }
142
143 attr.parse_nested_meta(|meta| {
144 if meta.path.is_ident("rename") {
145 let value: syn::LitStr = meta.value()?.parse()?;
146 result.rename = Some(value.value());
147 } else if meta.path.is_ident("rename_all") {
148 let value: syn::LitStr = meta.value()?.parse()?;
149 let s = value.value();
150 result.rename_all = RenameAll::from_str(&s);
151 if result.rename_all.is_none() {
152 return Err(syn::Error::new_spanned(
153 value,
154 format!(
155 "unknown rename_all value: '{}'. Expected one of: \
156 camelCase, PascalCase, snake_case, \
157 SCREAMING_SNAKE_CASE, kebab-case, SCREAMING-KEBAB-CASE",
158 s
159 ),
160 ));
161 }
162 } else if meta.path.is_ident("transparent") {
163 result.transparent = true;
164 } else if meta.path.is_ident("tag") {
165 let value: syn::LitStr = meta.value()?.parse()?;
166 result.tag = Some(value.value());
167 } else if meta.path.is_ident("content") {
168 let value: syn::LitStr = meta.value()?.parse()?;
169 result.content = Some(value.value());
170 } else if meta.path.is_ident("untagged") {
171 result.untagged = true;
172 } else if meta.path.is_ident("pattern") {
173 let value: syn::LitStr = meta.value()?.parse()?;
174 result.pattern = Some(value.value());
175 } else if meta.path.is_ident("namespace") {
176 let value: syn::LitStr = meta.value()?.parse()?;
177 let ns_str = value.value();
179 result.namespace = ns_str
180 .split(|c| c == ':' || c == '.')
181 .filter(|s| !s.is_empty())
182 .map(|s| s.to_string())
183 .collect();
184 }
185 Ok(())
186 })?;
187 }
188
189 Ok(result)
190 }
191}
192
193#[derive(Default)]
195struct FieldAttrs {
196 rename: Option<String>,
198 skip: bool,
200 flatten: bool,
202 type_override: Option<String>,
204 default: bool,
206 inline: bool,
208 index: Option<String>,
210 key: Option<String>,
212 pattern: Option<String>,
214}
215
216impl FieldAttrs {
217 fn from_attrs(attrs: &[Attribute]) -> syn::Result<Self> {
218 let mut result = FieldAttrs::default();
219
220 for attr in attrs {
221 if !attr.path().is_ident("ts") {
222 continue;
223 }
224
225 attr.parse_nested_meta(|meta| {
226 if meta.path.is_ident("rename") {
227 let value: syn::LitStr = meta.value()?.parse()?;
228 result.rename = Some(value.value());
229 } else if meta.path.is_ident("skip") {
230 result.skip = true;
231 } else if meta.path.is_ident("flatten") {
232 result.flatten = true;
233 } else if meta.path.is_ident("type") {
234 let value: syn::LitStr = meta.value()?.parse()?;
235 result.type_override = Some(value.value());
236 } else if meta.path.is_ident("default") {
237 result.default = true;
238 } else if meta.path.is_ident("inline") {
239 result.inline = true;
240 } else if meta.path.is_ident("index") {
241 let value: syn::LitStr = meta.value()?.parse()?;
242 result.index = Some(value.value());
243 } else if meta.path.is_ident("key") {
244 let value: syn::LitStr = meta.value()?.parse()?;
245 result.key = Some(value.value());
246 } else if meta.path.is_ident("pattern") {
247 let value: syn::LitStr = meta.value()?.parse()?;
248 result.pattern = Some(value.value());
249 }
250 Ok(())
251 })?;
252 }
253
254 Ok(result)
255 }
256
257 fn has_indexed_access(&self) -> bool {
259 self.index.is_some() && self.key.is_some()
260 }
261
262 fn has_pattern(&self) -> bool {
264 self.pattern.is_some()
265 }
266}
267
268fn get_field_name(
270 original: &str,
271 field_attrs: &FieldAttrs,
272 container_attrs: &ContainerAttrs,
273) -> String {
274 if let Some(ref renamed) = field_attrs.rename {
276 return renamed.clone();
277 }
278
279 if let Some(rename_all) = container_attrs.rename_all {
281 return rename_all.apply(original);
282 }
283
284 original.to_string()
286}
287
288fn parse_template_pattern(pattern: &str) -> syn::Result<(Vec<String>, Vec<String>)> {
298 let mut strings = Vec::new();
299 let mut types = Vec::new();
300 let mut current = String::new();
301 let mut chars = pattern.chars().peekable();
302
303 while let Some(c) = chars.next() {
304 if c == '$' && chars.peek() == Some(&'{') {
305 strings.push(std::mem::take(&mut current));
307 chars.next(); let mut type_name = String::new();
310 let mut depth = 1;
311 while let Some(tc) = chars.next() {
312 if tc == '{' {
313 depth += 1;
314 type_name.push(tc);
315 } else if tc == '}' {
316 depth -= 1;
317 if depth == 0 {
318 break;
319 }
320 type_name.push(tc);
321 } else {
322 type_name.push(tc);
323 }
324 }
325
326 if type_name.is_empty() {
327 return Err(syn::Error::new(
328 proc_macro2::Span::call_site(),
329 "Empty type placeholder ${} in pattern",
330 ));
331 }
332 types.push(type_name);
333 } else {
334 current.push(c);
335 }
336 }
337
338 strings.push(current);
340
341 Ok((strings, types))
342}
343
344fn type_name_to_typedef(name: &str) -> TokenStream2 {
347 match name.trim() {
348 "string" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::String) },
349 "number" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Number) },
350 "boolean" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Boolean) },
351 "bigint" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::BigInt) },
352 "any" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Any) },
353 "unknown" => quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Unknown) },
354 other => {
356 let type_ref = other.trim();
357 quote! { ferro_type::TypeDef::Ref(#type_ref.to_string()) }
358 }
359 }
360}
361
362fn generate_template_literal_expr(strings: &[String], types: &[String]) -> TokenStream2 {
364 let string_literals: Vec<_> = strings.iter().map(|s| quote! { #s.to_string() }).collect();
365 let type_exprs: Vec<_> = types.iter().map(|t| {
366 let typedef = type_name_to_typedef(t);
367 quote! { Box::new(#typedef) }
368 }).collect();
369
370 quote! {
371 ferro_type::TypeDef::TemplateLiteral {
372 strings: vec![#(#string_literals),*],
373 types: vec![#(#type_exprs),*],
374 }
375 }
376}
377
378#[proc_macro_derive(TypeScript, attributes(ts))]
424pub fn derive_typescript(input: TokenStream) -> TokenStream {
425 let input = parse_macro_input!(input as DeriveInput);
426
427 match expand_derive_typescript(&input) {
428 Ok(tokens) => tokens.into(),
429 Err(err) => err.to_compile_error().into(),
430 }
431}
432
433fn expand_derive_typescript(input: &DeriveInput) -> syn::Result<TokenStream2> {
434 let name = &input.ident;
435 let generics = &input.generics;
436
437 let container_attrs = ContainerAttrs::from_attrs(&input.attrs)?;
439
440 let type_name = container_attrs
442 .rename
443 .clone()
444 .unwrap_or_else(|| name.to_string());
445
446 match &input.data {
447 Data::Enum(data) => {
448 let typedef = generate_enum_typedef(&data.variants, &container_attrs)?;
449 generate_impl(name, &type_name, &container_attrs.namespace, generics, typedef)
450 }
451 Data::Struct(data) => {
452 if container_attrs.transparent {
454 if let syn::Fields::Unnamed(fields) = &data.fields {
455 if fields.unnamed.len() == 1 {
456 let inner_type = &fields.unnamed.first().unwrap().ty;
457 return generate_transparent_impl(name, inner_type, generics);
458 }
459 }
460 return Err(syn::Error::new_spanned(
461 input,
462 "#[ts(transparent)] can only be used on newtype structs (single unnamed field)",
463 ));
464 }
465
466 if let Some(ref pattern) = container_attrs.pattern {
468 let (strings, types) = parse_template_pattern(pattern)?;
469 let typedef = generate_template_literal_expr(&strings, &types);
470 return generate_impl(name, &type_name, &[], generics, typedef);
471 }
472
473 let typedef = generate_struct_typedef(&data.fields, &container_attrs)?;
474 generate_impl(name, &type_name, &container_attrs.namespace, generics, typedef)
475 }
476 Data::Union(_) => {
477 Err(syn::Error::new_spanned(
478 input,
479 "TypeScript derive is not supported for unions",
480 ))
481 }
482 }
483}
484
485fn generate_enum_typedef(
486 variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
487 container_attrs: &ContainerAttrs,
488) -> syn::Result<TokenStream2> {
489 if variants.is_empty() {
490 return Err(syn::Error::new(
491 proc_macro2::Span::call_site(),
492 "Cannot derive TypeScript for empty enum",
493 ));
494 }
495
496 let all_unit = variants.iter().all(|v| matches!(v.fields, Fields::Unit));
498
499 if container_attrs.untagged {
501 return generate_untagged_enum(variants, container_attrs);
502 }
503
504 let tag_name = container_attrs.tag.as_deref().unwrap_or("type");
506
507 let content_name = container_attrs.content.as_deref();
509
510 if all_unit {
511 let mut variant_exprs: Vec<TokenStream2> = Vec::new();
513 for v in variants.iter() {
514 let variant_attrs = FieldAttrs::from_attrs(&v.attrs)?;
515 let name = get_field_name(&v.ident.to_string(), &variant_attrs, container_attrs);
516 variant_exprs.push(
517 quote! { ferro_type::TypeDef::Literal(ferro_type::Literal::String(#name.to_string())) }
518 );
519 }
520
521 Ok(quote! {
522 ferro_type::TypeDef::Union(vec![#(#variant_exprs),*])
523 })
524 } else {
525 let mut variant_exprs: Vec<TokenStream2> = Vec::new();
527
528 for variant in variants.iter() {
529 let variant_attrs = FieldAttrs::from_attrs(&variant.attrs)?;
530 let variant_name_str = get_field_name(
531 &variant.ident.to_string(),
532 &variant_attrs,
533 container_attrs,
534 );
535
536 let expr = match &variant.fields {
537 Fields::Unit => {
538 quote! {
540 ferro_type::TypeDef::Object(vec![
541 ferro_type::Field::new(
542 #tag_name,
543 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
544 )
545 ])
546 }
547 }
548 Fields::Unnamed(fields) => {
549 if let Some(content) = content_name {
550 let content_type = if fields.unnamed.len() == 1 {
552 let field_type = &fields.unnamed.first().unwrap().ty;
553 type_to_typedef(field_type)
554 } else {
555 let field_exprs: Vec<TokenStream2> = fields
556 .unnamed
557 .iter()
558 .map(|f| type_to_typedef(&f.ty))
559 .collect();
560 quote! { ferro_type::TypeDef::Tuple(vec![#(#field_exprs),*]) }
561 };
562 quote! {
563 ferro_type::TypeDef::Object(vec![
564 ferro_type::Field::new(
565 #tag_name,
566 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
567 ),
568 ferro_type::Field::new(#content, #content_type)
569 ])
570 }
571 } else if fields.unnamed.len() == 1 {
572 let field_type = &fields.unnamed.first().unwrap().ty;
574 let type_expr = type_to_typedef(field_type);
575 quote! {
576 ferro_type::TypeDef::Object(vec![
577 ferro_type::Field::new(
578 #tag_name,
579 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
580 ),
581 ferro_type::Field::new("value", #type_expr)
582 ])
583 }
584 } else {
585 let field_exprs: Vec<TokenStream2> = fields
587 .unnamed
588 .iter()
589 .map(|f| type_to_typedef(&f.ty))
590 .collect();
591 quote! {
592 ferro_type::TypeDef::Object(vec![
593 ferro_type::Field::new(
594 #tag_name,
595 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
596 ),
597 ferro_type::Field::new(
598 "value",
599 ferro_type::TypeDef::Tuple(vec![#(#field_exprs),*])
600 )
601 ])
602 }
603 }
604 }
605 Fields::Named(fields) => {
606 let mut field_exprs: Vec<TokenStream2> = Vec::new();
607 for f in fields.named.iter() {
608 let field_attrs = FieldAttrs::from_attrs(&f.attrs)?;
609 if field_attrs.skip {
610 continue;
611 }
612 let original_name = f.ident.as_ref().unwrap().to_string();
613 let field_name = field_attrs.rename.clone().unwrap_or(original_name);
614 let field_type = &f.ty;
615 let type_expr = type_to_typedef(field_type);
616 field_exprs.push(quote! {
617 ferro_type::Field::new(#field_name, #type_expr)
618 });
619 }
620
621 if let Some(content) = content_name {
622 quote! {
624 ferro_type::TypeDef::Object(vec![
625 ferro_type::Field::new(
626 #tag_name,
627 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
628 ),
629 ferro_type::Field::new(
630 #content,
631 ferro_type::TypeDef::Object(vec![#(#field_exprs),*])
632 )
633 ])
634 }
635 } else {
636 quote! {
638 ferro_type::TypeDef::Object({
639 let mut fields = vec![
640 ferro_type::Field::new(
641 #tag_name,
642 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
643 )
644 ];
645 fields.extend(vec![#(#field_exprs),*]);
646 fields
647 })
648 }
649 }
650 }
651 };
652 variant_exprs.push(expr);
653 }
654
655 Ok(quote! {
656 ferro_type::TypeDef::Union(vec![#(#variant_exprs),*])
657 })
658 }
659}
660
661fn generate_untagged_enum(
663 variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
664 container_attrs: &ContainerAttrs,
665) -> syn::Result<TokenStream2> {
666 let mut variant_exprs: Vec<TokenStream2> = Vec::new();
667
668 for variant in variants.iter() {
669 let variant_attrs = FieldAttrs::from_attrs(&variant.attrs)?;
670 let variant_name_str = get_field_name(
671 &variant.ident.to_string(),
672 &variant_attrs,
673 container_attrs,
674 );
675
676 let expr = match &variant.fields {
677 Fields::Unit => {
678 quote! {
680 ferro_type::TypeDef::Literal(ferro_type::Literal::String(#variant_name_str.to_string()))
681 }
682 }
683 Fields::Unnamed(fields) => {
684 if fields.unnamed.len() == 1 {
685 let field_type = &fields.unnamed.first().unwrap().ty;
687 type_to_typedef(field_type)
688 } else {
689 let field_exprs: Vec<TokenStream2> = fields
691 .unnamed
692 .iter()
693 .map(|f| type_to_typedef(&f.ty))
694 .collect();
695 quote! {
696 ferro_type::TypeDef::Tuple(vec![#(#field_exprs),*])
697 }
698 }
699 }
700 Fields::Named(fields) => {
701 let mut field_exprs: Vec<TokenStream2> = Vec::new();
703 for f in fields.named.iter() {
704 let field_attrs = FieldAttrs::from_attrs(&f.attrs)?;
705 if field_attrs.skip {
706 continue;
707 }
708 let original_name = f.ident.as_ref().unwrap().to_string();
709 let field_name = field_attrs.rename.clone().unwrap_or(original_name);
710 let field_type = &f.ty;
711 let type_expr = type_to_typedef(field_type);
712 field_exprs.push(quote! {
713 ferro_type::Field::new(#field_name, #type_expr)
714 });
715 }
716 quote! {
717 ferro_type::TypeDef::Object(vec![#(#field_exprs),*])
718 }
719 }
720 };
721 variant_exprs.push(expr);
722 }
723
724 Ok(quote! {
725 ferro_type::TypeDef::Union(vec![#(#variant_exprs),*])
726 })
727}
728
729fn generate_struct_typedef(
730 fields: &syn::Fields,
731 container_attrs: &ContainerAttrs,
732) -> syn::Result<TokenStream2> {
733 match fields {
734 syn::Fields::Named(fields) => {
735 if fields.named.is_empty() {
737 return Ok(quote! { ferro_type::TypeDef::Object(vec![]) });
739 }
740
741 let mut regular_field_exprs: Vec<TokenStream2> = Vec::new();
743 let mut flatten_exprs: Vec<TokenStream2> = Vec::new();
744
745 for f in fields.named.iter() {
746 let field_attrs = FieldAttrs::from_attrs(&f.attrs)?;
747 if field_attrs.skip {
749 continue;
750 }
751
752 let field_type = &f.ty;
753
754 if field_attrs.index.is_some() != field_attrs.key.is_some() {
756 return Err(syn::Error::new_spanned(
757 f,
758 "#[ts(index = \"...\")] and #[ts(key = \"...\")] must be used together",
759 ));
760 }
761
762 if field_attrs.flatten {
763 flatten_exprs.push(quote! {
765 {
766 let inner_td = <#field_type as ferro_type::TypeScript>::typescript();
767 ferro_type::extract_object_fields(&inner_td)
768 }
769 });
770 } else {
771 let original_name = f.ident.as_ref().unwrap().to_string();
772 let field_name = get_field_name(&original_name, &field_attrs, container_attrs);
773
774 let type_expr = if let Some(ref type_override) = field_attrs.type_override {
776 quote! { ferro_type::TypeDef::Ref(#type_override.to_string()) }
777 } else if field_attrs.has_indexed_access() {
778 let index_base = field_attrs.index.as_ref().unwrap();
780 let index_key = field_attrs.key.as_ref().unwrap();
781 quote! {
782 ferro_type::TypeDef::IndexedAccess {
783 base: #index_base.to_string(),
784 key: #index_key.to_string(),
785 }
786 }
787 } else if field_attrs.has_pattern() {
788 let pattern = field_attrs.pattern.as_ref().unwrap();
790 let (strings, types) = parse_template_pattern(pattern)?;
791 generate_template_literal_expr(&strings, &types)
792 } else {
793 let base_expr = type_to_typedef(field_type);
794 if field_attrs.inline {
795 quote! { ferro_type::inline_typedef(#base_expr) }
796 } else {
797 base_expr
798 }
799 };
800
801 if field_attrs.default {
803 regular_field_exprs.push(quote! {
804 ferro_type::Field::optional(#field_name, #type_expr)
805 });
806 } else {
807 regular_field_exprs.push(quote! {
808 ferro_type::Field::new(#field_name, #type_expr)
809 });
810 }
811 }
812 }
813
814 if flatten_exprs.is_empty() {
816 Ok(quote! {
817 ferro_type::TypeDef::Object(vec![#(#regular_field_exprs),*])
818 })
819 } else {
820 Ok(quote! {
821 {
822 let mut fields = vec![#(#regular_field_exprs),*];
823 #(fields.extend(#flatten_exprs);)*
824 ferro_type::TypeDef::Object(fields)
825 }
826 })
827 }
828 }
829 syn::Fields::Unnamed(fields) => {
830 if fields.unnamed.len() == 1 {
832 let field_type = &fields.unnamed.first().unwrap().ty;
834 let type_expr = type_to_typedef(field_type);
835 Ok(quote! { #type_expr })
836 } else {
837 let field_exprs: Vec<TokenStream2> = fields
839 .unnamed
840 .iter()
841 .map(|f| type_to_typedef(&f.ty))
842 .collect();
843
844 Ok(quote! {
845 ferro_type::TypeDef::Tuple(vec![#(#field_exprs),*])
846 })
847 }
848 }
849 syn::Fields::Unit => {
850 Ok(quote! { ferro_type::TypeDef::Primitive(ferro_type::Primitive::Null) })
852 }
853 }
854}
855
856fn type_to_typedef(ty: &Type) -> TokenStream2 {
859 quote! { <#ty as ferro_type::TypeScript>::typescript() }
860}
861
862fn generate_transparent_impl(
865 name: &Ident,
866 inner_type: &Type,
867 generics: &Generics,
868) -> syn::Result<TokenStream2> {
869 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
870
871 let where_clause = if generics.params.is_empty() {
873 where_clause.cloned()
874 } else {
875 let type_params: Vec<_> = generics.params.iter().filter_map(|p| {
876 if let GenericParam::Type(tp) = p {
877 Some(&tp.ident)
878 } else {
879 None
880 }
881 }).collect();
882
883 if type_params.is_empty() {
884 where_clause.cloned()
885 } else {
886 let bounds = type_params.iter().map(|p| {
887 quote! { #p: ferro_type::TypeScript }
888 });
889
890 if let Some(existing_where) = where_clause {
891 let existing_predicates = &existing_where.predicates;
892 Some(syn::parse_quote! { where #(#bounds,)* #existing_predicates })
893 } else {
894 Some(syn::parse_quote! { where #(#bounds),* })
895 }
896 }
897 };
898
899 Ok(quote! {
900 impl #impl_generics ferro_type::TypeScript for #name #ty_generics #where_clause {
901 fn typescript() -> ferro_type::TypeDef {
902 <#inner_type as ferro_type::TypeScript>::typescript()
903 }
904 }
905 })
906}
907
908fn generate_impl(
909 name: &Ident,
910 name_str: &str,
911 namespace: &[String],
912 generics: &Generics,
913 typedef_expr: TokenStream2,
914) -> syn::Result<TokenStream2> {
915 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
916
917 let where_clause = if generics.params.is_empty() {
919 where_clause.cloned()
920 } else {
921 let type_params: Vec<_> = generics.params.iter().filter_map(|p| {
922 if let GenericParam::Type(tp) = p {
923 Some(&tp.ident)
924 } else {
925 None
926 }
927 }).collect();
928
929 if type_params.is_empty() {
930 where_clause.cloned()
931 } else {
932 let bounds = type_params.iter().map(|p| {
933 quote! { #p: ferro_type::TypeScript }
934 });
935
936 if let Some(existing_where) = where_clause {
937 let existing_predicates = &existing_where.predicates;
938 Some(syn::parse_quote! { where #(#bounds,)* #existing_predicates })
939 } else {
940 Some(syn::parse_quote! { where #(#bounds),* })
941 }
942 }
943 };
944
945 let registration = if generics.params.is_empty() {
948 let register_name = syn::Ident::new(
949 &format!("__FERRO_TYPE_REGISTER_{}", name.to_string().to_uppercase()),
950 name.span(),
951 );
952 quote! {
953 #[ferro_type::linkme::distributed_slice(ferro_type::TYPESCRIPT_TYPES)]
954 #[linkme(crate = ferro_type::linkme)]
955 static #register_name: fn() -> ferro_type::TypeDef = || <#name as ferro_type::TypeScript>::typescript();
956 }
957 } else {
958 quote! {}
959 };
960
961 let namespace_expr = if namespace.is_empty() {
963 quote! { vec![] }
964 } else {
965 let ns_strings = namespace.iter().map(|s| quote! { #s.to_string() });
966 quote! { vec![#(#ns_strings),*] }
967 };
968
969 Ok(quote! {
970 impl #impl_generics ferro_type::TypeScript for #name #ty_generics #where_clause {
971 fn typescript() -> ferro_type::TypeDef {
972 ferro_type::TypeDef::Named {
973 namespace: #namespace_expr,
974 name: #name_str.to_string(),
975 def: Box::new(#typedef_expr),
976 module: Some(module_path!().to_string()),
977 }
978 }
979 }
980
981 #registration
982 })
983}