Skip to main content

specta_typescript/
primitives.rs

1//! Primitives provide building blocks for Specta-based libraries.
2//!
3//! These are for advanced usecases, you should generally use [crate::Typescript] or
4//! [crate::JSDoc] in end-user applications.
5
6use std::{borrow::Cow, cell::RefCell, fmt::Write as _, iter};
7
8use specta::{
9    ResolvedTypes, Types,
10    datatype::{
11        DataType, Deprecated, Enum, Fields, GenericReference, List, Map, NamedDataType,
12        NamedReference, OpaqueReference, Primitive, Reference, Tuple, Variant,
13    },
14};
15
16use crate::{
17    Branded, BrandedTypeExporter, Error, Exporter, Layout,
18    legacy::{
19        ExportContext, deprecated_details, escape_jsdoc_text, escape_typescript_string_literal,
20        is_identifier, js_doc,
21    },
22    map_keys, opaque,
23};
24
25/// Generate a group of `export Type = ...` Typescript string for a specific [`NamedDataType`].
26///
27/// This method leaves the following up to the implementer:
28///  - Ensuring all referenced types are exported
29///  - Handling multiple type with overlapping names
30///  - Transforming the type for your serialization format (Eg. Serde)
31///
32/// We recommend passing in your types in bulk instead of doing individual calls as it leaves formatting to us and also allows us to merge the JSDoc types into a single large comment.
33///
34pub fn export<'a>(
35    exporter: &dyn AsRef<Exporter>,
36    types: &ResolvedTypes,
37    ndts: impl Iterator<Item = &'a NamedDataType>,
38    indent: &str,
39) -> Result<String, Error> {
40    let mut s = String::new();
41    export_internal(&mut s, exporter.as_ref(), types.as_types(), ndts, indent)?;
42    Ok(s)
43}
44
45pub(crate) fn export_internal<'a>(
46    s: &mut String,
47    exporter: &Exporter,
48    types: &Types,
49    ndts: impl Iterator<Item = &'a NamedDataType>,
50    indent: &str,
51) -> Result<(), Error> {
52    if exporter.jsdoc {
53        let mut ndts = ndts.peekable();
54        if ndts.peek().is_none() {
55            return Ok(());
56        }
57
58        s.push_str(indent);
59        s.push_str("/**\n");
60
61        for (index, ndt) in ndts.enumerate() {
62            if index != 0 {
63                s.push_str(indent);
64                s.push_str("\t*\n");
65            }
66
67            append_typedef_body(s, exporter, types, ndt, indent)?;
68        }
69
70        s.push_str(indent);
71        s.push_str("\t*/\n");
72        return Ok(());
73    }
74
75    for (index, ndt) in ndts.enumerate() {
76        if index != 0 {
77            s.push('\n');
78        }
79
80        export_single_internal(s, exporter, types, ndt, indent)?;
81    }
82
83    Ok(())
84}
85
86fn export_single_internal(
87    s: &mut String,
88    exporter: &Exporter,
89    types: &Types,
90    ndt: &NamedDataType,
91    indent: &str,
92) -> Result<(), Error> {
93    if exporter.jsdoc {
94        let mut typedef = String::new();
95        typedef_internal(&mut typedef, exporter, types, ndt)?;
96        for line in typedef.lines() {
97            s.push_str(indent);
98            s.push_str(line);
99            s.push('\n');
100        }
101        return Ok(());
102    }
103
104    let generics = (!ndt.generics().is_empty())
105        .then(|| {
106            iter::once("<")
107                .chain(intersperse(
108                    ndt.generics().iter().map(|(_, g)| g.as_ref()),
109                    ", ",
110                ))
111                .chain(iter::once(">"))
112        })
113        .into_iter()
114        .flatten();
115
116    // TODO: Modernise this
117    let name = crate::legacy::sanitise_type_name(
118        crate::legacy::ExportContext {
119            cfg: exporter,
120            path: vec![],
121        },
122        &match exporter.layout {
123            Layout::ModulePrefixedName => {
124                let mut s = ndt.module_path().split("::").collect::<Vec<_>>().join("_");
125                s.push('_');
126                s.push_str(ndt.name());
127                Cow::Owned(s)
128            }
129            _ => ndt.name().clone(),
130        },
131    )?;
132
133    let mut comments = String::new();
134    js_doc(&mut comments, ndt.docs(), ndt.deprecated(), !exporter.jsdoc);
135    if !comments.is_empty() {
136        for line in comments.lines() {
137            s.push_str(indent);
138            s.push_str(line);
139            s.push('\n');
140        }
141    }
142
143    s.push_str(indent);
144    s.push_str("export type ");
145    s.push_str(&name);
146    for part in generics {
147        s.push_str(part);
148    }
149    s.push_str(" = ");
150
151    let _generic_scope = push_generic_scope(ndt.generics());
152    datatype(
153        s,
154        exporter,
155        types,
156        ndt.ty(),
157        vec![ndt.name().clone()],
158        Some(ndt.name()),
159        indent,
160        Default::default(),
161    )?;
162    s.push_str(";\n");
163
164    Ok(())
165}
166
167/// Generate an inlined Typescript string for a specific [`DataType`].
168///
169/// This methods leaves all the same things as the [`export`] method up to the user.
170///
171/// Note that calling this method with a tagged struct or enum may cause the tag to not be exported.
172/// The type should be wrapped in a [`NamedDataType`] to provide a proper name.
173///
174pub fn inline(
175    exporter: &dyn AsRef<Exporter>,
176    types: &ResolvedTypes,
177    dt: &DataType,
178) -> Result<String, Error> {
179    let mut s = String::new();
180    inline_datatype(
181        &mut s,
182        exporter.as_ref(),
183        types.as_types(),
184        dt,
185        vec![],
186        None,
187        "",
188        0,
189        &[],
190    )?;
191    Ok(s)
192}
193
194// This can be used internally to prevent cloning `Typescript` instances.
195// Externally this shouldn't be a concern so we don't expose it.
196pub(crate) fn typedef_internal(
197    s: &mut String,
198    exporter: &Exporter,
199    types: &Types,
200    dt: &NamedDataType,
201) -> Result<(), Error> {
202    s.push_str("/**\n");
203    append_typedef_body(s, exporter, types, dt, "")?;
204
205    s.push_str("\t*/");
206
207    Ok(())
208}
209
210fn append_jsdoc_properties(
211    s: &mut String,
212    exporter: &Exporter,
213    types: &Types,
214    dt: &NamedDataType,
215    indent: &str,
216) -> Result<(), Error> {
217    match dt.ty() {
218        DataType::Struct(strct) => match strct.fields() {
219            Fields::Unit => {}
220            Fields::Unnamed(unnamed) => {
221                for (idx, field) in unnamed.fields().iter().enumerate() {
222                    let Some(ty) = field.ty() else {
223                        continue;
224                    };
225
226                    let mut ty_str = String::new();
227                    let datatype_prefix = format!("{indent}\t*\t");
228                    datatype(
229                        &mut ty_str,
230                        exporter,
231                        types,
232                        ty,
233                        vec![dt.name().clone(), idx.to_string().into()],
234                        Some(dt.name()),
235                        &datatype_prefix,
236                        Default::default(),
237                    )?;
238
239                    push_jsdoc_property(
240                        s,
241                        &ty_str,
242                        &idx.to_string(),
243                        field.optional(),
244                        field.docs(),
245                        field.deprecated(),
246                        indent,
247                    );
248                }
249            }
250            Fields::Named(named) => {
251                for (name, field) in named.fields() {
252                    let Some(ty) = field.ty() else {
253                        continue;
254                    };
255
256                    let mut ty_str = String::new();
257                    let datatype_prefix = format!("{indent}\t*\t");
258                    datatype(
259                        &mut ty_str,
260                        exporter,
261                        types,
262                        ty,
263                        vec![dt.name().clone(), name.clone()],
264                        Some(dt.name()),
265                        &datatype_prefix,
266                        Default::default(),
267                    )?;
268
269                    push_jsdoc_property(
270                        s,
271                        &ty_str,
272                        name,
273                        field.optional(),
274                        field.docs(),
275                        field.deprecated(),
276                        indent,
277                    );
278                }
279            }
280        },
281        DataType::Enum(enm) => {
282            for (variant_name, variant) in enm.variants().iter().filter(|(_, v)| !v.skip()) {
283                let mut one_variant_enum = enm.clone();
284                one_variant_enum
285                    .variants_mut()
286                    .retain(|(name, _)| name == variant_name);
287
288                let mut variant_ty = String::new();
289                crate::legacy::enum_datatype(
290                    ExportContext {
291                        cfg: exporter,
292                        path: vec![],
293                    },
294                    &one_variant_enum,
295                    types,
296                    &mut variant_ty,
297                    "",
298                    &[],
299                )?;
300
301                push_jsdoc_property(
302                    s,
303                    &variant_ty,
304                    variant_name,
305                    false,
306                    variant.docs(),
307                    variant.deprecated(),
308                    indent,
309                );
310            }
311        }
312        _ => {}
313    }
314
315    Ok(())
316}
317
318fn push_jsdoc_property(
319    s: &mut String,
320    ty: &str,
321    name: &str,
322    optional: bool,
323    docs: &str,
324    deprecated: Option<&Deprecated>,
325    indent: &str,
326) {
327    s.push_str(indent);
328    s.push_str("\t* @property {");
329    push_jsdoc_type(s, ty, indent);
330    s.push_str("} ");
331    s.push_str(&jsdoc_property_name(name, optional));
332
333    if let Some(description) = jsdoc_description(docs, deprecated) {
334        s.push_str(" - ");
335        s.push_str(&description);
336    }
337
338    s.push('\n');
339}
340
341fn push_jsdoc_type(s: &mut String, ty: &str, indent: &str) {
342    let mut lines = ty.lines();
343    if let Some(first_line) = lines.next() {
344        s.push_str(first_line);
345    }
346
347    for line in lines {
348        s.push('\n');
349
350        if line
351            .strip_prefix(indent)
352            .is_some_and(|rest| rest.starts_with("\t*"))
353        {
354            s.push_str(line);
355        } else {
356            s.push_str(indent);
357            s.push_str("\t* ");
358            s.push_str(line);
359        }
360    }
361}
362
363fn jsdoc_property_name(name: &str, optional: bool) -> String {
364    let name = if is_identifier(name) {
365        name.to_string()
366    } else {
367        format!("\"{}\"", escape_typescript_string_literal(name))
368    };
369
370    if optional { format!("[{name}]") } else { name }
371}
372
373fn append_typedef_body(
374    s: &mut String,
375    exporter: &Exporter,
376    types: &Types,
377    dt: &NamedDataType,
378    indent: &str,
379) -> Result<(), Error> {
380    let generics = (!dt.generics().is_empty())
381        .then(|| {
382            iter::once("<")
383                .chain(intersperse(
384                    dt.generics().iter().map(|(_, g)| g.as_ref()),
385                    ", ",
386                ))
387                .chain(iter::once(">"))
388        })
389        .into_iter()
390        .flatten();
391
392    let name = dt.name();
393    let type_name = iter::empty()
394        .chain([name.as_ref()])
395        .chain(generics)
396        .collect::<String>();
397
398    let mut typedef_ty = String::new();
399    let datatype_prefix = format!("{indent}\t*\t");
400    let _generic_scope = push_generic_scope(dt.generics());
401    datatype(
402        &mut typedef_ty,
403        exporter,
404        types,
405        dt.ty(),
406        vec![dt.name().clone()],
407        Some(dt.name()),
408        &datatype_prefix,
409        Default::default(),
410    )?;
411
412    if !dt.docs().is_empty() {
413        for line in dt.docs().lines() {
414            s.push_str(indent);
415            s.push_str("\t* ");
416            s.push_str(&escape_jsdoc_text(line));
417            s.push('\n');
418        }
419        s.push_str(indent);
420        s.push_str("\t*\n");
421    }
422
423    if let Some(deprecated) = dt.deprecated() {
424        s.push_str(indent);
425        s.push_str("\t* @deprecated");
426        if let Some(details) = deprecated_details(deprecated) {
427            s.push(' ');
428            s.push_str(&details);
429        }
430        s.push('\n');
431    }
432
433    s.push_str(indent);
434    s.push_str("\t* @typedef {");
435    push_jsdoc_type(s, &typedef_ty, indent);
436    s.push_str("} ");
437    s.push_str(&type_name);
438    s.push('\n');
439
440    append_jsdoc_properties(s, exporter, types, dt, indent)?;
441
442    Ok(())
443}
444
445fn jsdoc_description(docs: &str, deprecated: Option<&Deprecated>) -> Option<String> {
446    let docs = docs
447        .lines()
448        .map(str::trim)
449        .filter(|line| !line.is_empty())
450        .map(|line| escape_jsdoc_text(line).into_owned())
451        .collect::<Vec<_>>()
452        .join(" ");
453
454    let deprecated = deprecated.map(|deprecated| {
455        let mut value = String::from("@deprecated");
456        if let Some(details) = deprecated_details(deprecated) {
457            value.push(' ');
458            value.push_str(&escape_jsdoc_text(&details));
459        }
460        value
461    });
462
463    match (docs.is_empty(), deprecated) {
464        (true, None) => None,
465        (true, Some(deprecated)) => Some(deprecated),
466        (false, None) => Some(docs),
467        (false, Some(deprecated)) => Some(format!("{docs} {deprecated}")),
468    }
469}
470
471/// Generate an Typescript string to refer to a specific [`DataType`].
472///
473/// For primitives this will include the literal type but for named type it will contain a reference.
474///
475/// See [`export`] for the list of things to consider when using this.
476pub fn reference(
477    exporter: &dyn AsRef<Exporter>,
478    types: &ResolvedTypes,
479    r: &Reference,
480) -> Result<String, Error> {
481    let mut s = String::new();
482    reference_dt(
483        &mut s,
484        exporter.as_ref(),
485        types.as_types(),
486        r,
487        vec![],
488        "",
489        &[],
490    )?;
491    Ok(s)
492}
493
494pub(crate) fn datatype_with_inline_attr(
495    s: &mut String,
496    exporter: &Exporter,
497    types: &Types,
498    dt: &DataType,
499    location: Vec<Cow<'static, str>>,
500    parent_name: Option<&str>,
501    prefix: &str,
502    generics: &[(GenericReference, DataType)],
503    inline: bool,
504) -> Result<(), Error> {
505    if inline {
506        return shallow_inline_datatype(
507            s,
508            exporter,
509            types,
510            dt,
511            location,
512            parent_name,
513            prefix,
514            generics,
515        );
516    }
517
518    datatype(
519        s,
520        exporter,
521        types,
522        dt,
523        location,
524        parent_name,
525        prefix,
526        generics,
527    )
528}
529
530fn merged_generics(
531    parent: &[(GenericReference, DataType)],
532    child: &[(GenericReference, DataType)],
533) -> Vec<(GenericReference, DataType)> {
534    let unshadowed_parent = parent
535        .iter()
536        .filter(|(parent_generic, _)| {
537            !child
538                .iter()
539                .any(|(child_generic, _)| child_generic == parent_generic)
540        })
541        .cloned();
542
543    child
544        .iter()
545        .map(|(generic, dt)| (generic.clone(), resolve_generics_in_datatype(dt, parent)))
546        .chain(unshadowed_parent)
547        .collect()
548}
549
550thread_local! {
551    static INLINE_REFERENCE_STACK: RefCell<Vec<(Cow<'static, str>, Cow<'static, str>, Vec<(GenericReference, DataType)>)>> = const { RefCell::new(Vec::new()) };
552    static RESOLVING_GENERICS: RefCell<Vec<GenericReference>> = const { RefCell::new(Vec::new()) };
553    static GENERIC_NAME_STACK: RefCell<Vec<Vec<(GenericReference, Cow<'static, str>)>>> = const { RefCell::new(Vec::new()) };
554}
555
556struct GenericScopeGuard;
557
558impl Drop for GenericScopeGuard {
559    fn drop(&mut self) {
560        GENERIC_NAME_STACK.with(|stack| {
561            stack.borrow_mut().pop();
562        });
563    }
564}
565
566fn push_generic_scope(generics: &[(GenericReference, Cow<'static, str>)]) -> GenericScopeGuard {
567    GENERIC_NAME_STACK.with(|stack| {
568        stack.borrow_mut().push(generics.to_vec());
569    });
570    GenericScopeGuard
571}
572
573fn resolve_generic_name(generic: &GenericReference) -> Option<Cow<'static, str>> {
574    GENERIC_NAME_STACK.with(|stack| {
575        stack.borrow().iter().rev().find_map(|scope| {
576            scope
577                .iter()
578                .find(|(candidate, _)| candidate == generic)
579                .map(|(_, name)| name.clone())
580        })
581    })
582}
583
584fn write_generic_reference(s: &mut String, generic: &GenericReference) -> Result<(), Error> {
585    let generic_name = resolve_generic_name(generic)
586        .ok_or_else(|| Error::unresolved_generic_reference(format!("{generic:?}")))?;
587    s.push_str(generic_name.as_ref());
588    Ok(())
589}
590
591fn shallow_inline_datatype(
592    s: &mut String,
593    exporter: &Exporter,
594    types: &Types,
595    dt: &DataType,
596    location: Vec<Cow<'static, str>>,
597    parent_name: Option<&str>,
598    prefix: &str,
599    generics: &[(GenericReference, DataType)],
600) -> Result<(), Error> {
601    match dt {
602        DataType::Primitive(p) => s.push_str(primitive_dt(p, location)?),
603        DataType::List(list) => {
604            let mut inner = String::new();
605            shallow_inline_datatype(
606                &mut inner,
607                exporter,
608                types,
609                list.ty(),
610                location,
611                parent_name,
612                prefix,
613                generics,
614            )?;
615
616            let inner = if (inner.contains(' ') && !inner.ends_with('}'))
617                || (inner.contains(' ') && (inner.contains('&') || inner.contains('|')))
618            {
619                format!("({inner})")
620            } else {
621                inner
622            };
623
624            if let Some(length) = list.length() {
625                s.push('[');
626                for i in 0..length {
627                    if i != 0 {
628                        s.push_str(", ");
629                    }
630                    s.push_str(&inner);
631                }
632                s.push(']');
633            } else {
634                write!(s, "{inner}[]")?;
635            }
636        }
637        DataType::Map(map) => {
638            let path = map_key_path(&location);
639            map_keys::validate_map_key(map.key_ty(), types, generics, format!("{path}.<map_key>"))?;
640            let rendered_key =
641                map_key_render_type(resolve_generics_in_datatype(map.key_ty(), generics));
642
643            fn is_exhaustive(dt: &DataType, types: &Types) -> bool {
644                match dt {
645                    DataType::Enum(e) => {
646                        e.variants().iter().filter(|(_, v)| !v.skip()).count() == 0
647                    }
648                    DataType::Reference(Reference::Named(r)) => r
649                        .get(types)
650                        .is_some_and(|ndt| is_exhaustive(ndt.ty(), types)),
651                    DataType::Reference(Reference::Opaque(_)) => false,
652                    _ => true,
653                }
654            }
655
656            let exhaustive = is_exhaustive(&rendered_key, types);
657            if !exhaustive {
658                s.push_str("Partial<");
659            }
660
661            s.push_str("{ [key in ");
662            shallow_inline_datatype(
663                s,
664                exporter,
665                types,
666                &rendered_key,
667                location.clone(),
668                parent_name,
669                prefix,
670                generics,
671            )?;
672            s.push_str("]: ");
673            shallow_inline_datatype(
674                s,
675                exporter,
676                types,
677                map.value_ty(),
678                location,
679                parent_name,
680                prefix,
681                generics,
682            )?;
683            s.push_str(" }");
684
685            if !exhaustive {
686                s.push('>');
687            }
688        }
689        DataType::Nullable(dt) => {
690            let mut inner = String::new();
691            shallow_inline_datatype(
692                &mut inner,
693                exporter,
694                types,
695                dt,
696                location,
697                parent_name,
698                prefix,
699                generics,
700            )?;
701
702            s.push_str(&inner);
703            if inner != "null" && !inner.ends_with(" | null") {
704                s.push_str(" | null");
705            }
706        }
707        DataType::Struct(st) => {
708            crate::legacy::struct_datatype(
709                crate::legacy::ExportContext {
710                    cfg: exporter,
711                    path: vec![],
712                },
713                parent_name,
714                st,
715                types,
716                s,
717                prefix,
718                generics,
719            )?;
720        }
721        DataType::Enum(enm) => {
722            crate::legacy::enum_datatype(
723                crate::legacy::ExportContext {
724                    cfg: exporter,
725                    path: vec![],
726                },
727                enm,
728                types,
729                s,
730                prefix,
731                generics,
732            )?;
733        }
734        DataType::Tuple(tuple) => match tuple.elements() {
735            [] => s.push_str("null"),
736            elements => {
737                s.push('[');
738                for (idx, dt) in elements.iter().enumerate() {
739                    if idx != 0 {
740                        s.push_str(", ");
741                    }
742                    shallow_inline_datatype(
743                        s,
744                        exporter,
745                        types,
746                        dt,
747                        location.clone(),
748                        parent_name,
749                        prefix,
750                        generics,
751                    )?;
752                }
753                s.push(']');
754            }
755        },
756        DataType::Reference(r) => match r {
757            Reference::Named(r) => {
758                let ndt = r
759                    .get(types)
760                    .ok_or_else(|| Error::dangling_named_reference(format!("{r:?}")))?;
761                let inline_key = (
762                    ndt.module_path().clone(),
763                    ndt.name().clone(),
764                    r.generics().to_vec(),
765                );
766                let already_inlining = INLINE_REFERENCE_STACK
767                    .with(|stack| stack.borrow().iter().any(|key| key == &inline_key));
768
769                if already_inlining {
770                    return reference_named_dt(s, exporter, types, r, location, prefix, generics);
771                }
772
773                INLINE_REFERENCE_STACK.with(|stack| stack.borrow_mut().push(inline_key));
774                let combined_generics = merged_generics(generics, r.generics());
775                let resolved = resolve_generics_in_datatype(ndt.ty(), &combined_generics);
776                let result = shallow_inline_datatype(
777                    s,
778                    exporter,
779                    types,
780                    &resolved,
781                    location,
782                    parent_name,
783                    prefix,
784                    &combined_generics,
785                );
786                INLINE_REFERENCE_STACK.with(|stack| {
787                    stack.borrow_mut().pop();
788                });
789
790                result
791            }
792            Reference::Generic(g) => {
793                if let Some((_, resolved_dt)) = generics.iter().find(|(ge, _)| ge == g) {
794                    if matches!(resolved_dt, DataType::Reference(Reference::Generic(inner)) if inner == g)
795                    {
796                        write_generic_reference(s, g)?;
797                    } else {
798                        let already_resolving = RESOLVING_GENERICS
799                            .with(|stack| stack.borrow().iter().any(|seen| seen == g));
800                        if already_resolving {
801                            write_generic_reference(s, g)?;
802                        } else {
803                            RESOLVING_GENERICS.with(|stack| stack.borrow_mut().push(g.clone()));
804                            let result = shallow_inline_datatype(
805                                s,
806                                exporter,
807                                types,
808                                resolved_dt,
809                                location,
810                                parent_name,
811                                prefix,
812                                generics,
813                            );
814                            RESOLVING_GENERICS.with(|stack| {
815                                stack.borrow_mut().pop();
816                            });
817                            result?;
818                        }
819                    }
820                } else {
821                    write_generic_reference(s, g)?;
822                }
823                Ok(())
824            }
825            Reference::Opaque(_) => reference_dt(s, exporter, types, r, location, prefix, generics),
826        }?,
827    }
828
829    Ok(())
830}
831
832fn resolve_generics_in_datatype(
833    dt: &DataType,
834    generics: &[(GenericReference, DataType)],
835) -> DataType {
836    fn resolve(
837        dt: &DataType,
838        generics: &[(GenericReference, DataType)],
839        visiting: &mut Vec<GenericReference>,
840    ) -> DataType {
841        match dt {
842            DataType::Primitive(_) => dt.clone(),
843            DataType::List(l) => {
844                let mut out = l.clone();
845                out.set_ty(resolve(l.ty(), generics, visiting));
846                DataType::List(out)
847            }
848            DataType::Map(m) => {
849                let mut out = m.clone();
850                out.set_key_ty(resolve(m.key_ty(), generics, visiting));
851                out.set_value_ty(resolve(m.value_ty(), generics, visiting));
852                DataType::Map(out)
853            }
854            DataType::Nullable(def) => {
855                DataType::Nullable(Box::new(resolve(def, generics, visiting)))
856            }
857            DataType::Struct(st) => {
858                let mut out = st.clone();
859                match out.fields_mut() {
860                    specta::datatype::Fields::Unit => {}
861                    specta::datatype::Fields::Unnamed(unnamed) => {
862                        for field in unnamed.fields_mut() {
863                            if let Some(ty) = field.ty_mut() {
864                                *ty = resolve(ty, generics, visiting);
865                            }
866                        }
867                    }
868                    specta::datatype::Fields::Named(named) => {
869                        for (_, field) in named.fields_mut() {
870                            if let Some(ty) = field.ty_mut() {
871                                *ty = resolve(ty, generics, visiting);
872                            }
873                        }
874                    }
875                }
876                DataType::Struct(out)
877            }
878            DataType::Enum(en) => {
879                let mut out = en.clone();
880                for (_, variant) in out.variants_mut() {
881                    match variant.fields_mut() {
882                        specta::datatype::Fields::Unit => {}
883                        specta::datatype::Fields::Unnamed(unnamed) => {
884                            for field in unnamed.fields_mut() {
885                                if let Some(ty) = field.ty_mut() {
886                                    *ty = resolve(ty, generics, visiting);
887                                }
888                            }
889                        }
890                        specta::datatype::Fields::Named(named) => {
891                            for (_, field) in named.fields_mut() {
892                                if let Some(ty) = field.ty_mut() {
893                                    *ty = resolve(ty, generics, visiting);
894                                }
895                            }
896                        }
897                    }
898                }
899                DataType::Enum(out)
900            }
901            DataType::Tuple(t) => {
902                let mut out = t.clone();
903                for element in out.elements_mut() {
904                    *element = resolve(element, generics, visiting);
905                }
906                DataType::Tuple(out)
907            }
908            DataType::Reference(Reference::Generic(g)) => {
909                if visiting.iter().any(|seen| seen == g) {
910                    return dt.clone();
911                }
912
913                if let Some((_, resolved_dt)) = generics.iter().find(|(ge, _)| ge == g) {
914                    if matches!(resolved_dt, DataType::Reference(Reference::Generic(inner)) if inner == g)
915                    {
916                        dt.clone()
917                    } else {
918                        visiting.push(g.clone());
919                        let out = resolve(resolved_dt, generics, visiting);
920                        visiting.pop();
921                        out
922                    }
923                } else {
924                    dt.clone()
925                }
926            }
927            DataType::Reference(_) => dt.clone(),
928        }
929    }
930
931    resolve(dt, generics, &mut Vec::new())
932}
933
934// Internal function to handle inlining without cloning DataType nodes
935fn inline_datatype(
936    s: &mut String,
937    exporter: &Exporter,
938    types: &Types,
939    dt: &DataType,
940    location: Vec<Cow<'static, str>>,
941    parent_name: Option<&str>,
942    prefix: &str,
943    depth: usize,
944    generics: &[(GenericReference, DataType)],
945) -> Result<(), Error> {
946    // Prevent infinite recursion
947    if depth == 25 {
948        return Err(Error::invalid_name(
949            location.join("."),
950            "Type recursion limit exceeded during inline expansion",
951        ));
952    }
953
954    match dt {
955        DataType::Primitive(p) => s.push_str(primitive_dt(p, location)?),
956        DataType::List(l) => {
957            // Inline the list element type
958            let mut dt_str = String::new();
959            crate::legacy::datatype_inner(
960                crate::legacy::ExportContext {
961                    cfg: exporter,
962                    path: vec![],
963                },
964                l.ty(),
965                types,
966                &mut dt_str,
967                generics,
968            )?;
969
970            let dt_str = if (dt_str.contains(' ') && !dt_str.ends_with('}'))
971                || (dt_str.contains(' ') && (dt_str.contains('&') || dt_str.contains('|')))
972            {
973                format!("({dt_str})")
974            } else {
975                dt_str
976            };
977
978            if let Some(length) = l.length() {
979                s.push('[');
980                for n in 0..length {
981                    if n != 0 {
982                        s.push_str(", ");
983                    }
984                    s.push_str(&dt_str);
985                }
986                s.push(']');
987            } else {
988                write!(s, "{dt_str}[]")?;
989            }
990        }
991        DataType::Map(m) => map_dt(s, exporter, types, m, location, generics)?,
992        DataType::Nullable(def) => {
993            let mut inner = String::new();
994            inline_datatype(
995                &mut inner,
996                exporter,
997                types,
998                def,
999                location,
1000                parent_name,
1001                prefix,
1002                depth + 1,
1003                generics,
1004            )?;
1005
1006            s.push_str(&inner);
1007            if inner != "null" && !inner.ends_with(" | null") {
1008                s.push_str(" | null");
1009            }
1010        }
1011        DataType::Struct(st) => {
1012            // If we have generics to resolve, handle the struct inline to preserve context
1013            if !generics.is_empty() {
1014                use specta::datatype::Fields;
1015                match st.fields() {
1016                    Fields::Unit => s.push_str("null"),
1017                    Fields::Named(named) => {
1018                        s.push('{');
1019                        let mut has_field = false;
1020                        for (key, field) in named.fields() {
1021                            // Skip fields without a type (e.g., flattened or skipped fields)
1022                            let Some(field_ty) = field.ty() else {
1023                                continue;
1024                            };
1025
1026                            has_field = true;
1027                            s.push('\n');
1028                            s.push_str(prefix);
1029                            s.push('\t');
1030                            s.push_str(key);
1031                            s.push_str(": ");
1032                            inline_datatype(
1033                                s,
1034                                exporter,
1035                                types,
1036                                field_ty,
1037                                location.clone(),
1038                                parent_name,
1039                                prefix,
1040                                depth + 1,
1041                                generics,
1042                            )?;
1043                            s.push(',');
1044                        }
1045
1046                        if has_field {
1047                            s.push('\n');
1048                            s.push_str(prefix);
1049                        }
1050
1051                        s.push('}');
1052                    }
1053                    Fields::Unnamed(_) => {
1054                        // For unnamed fields, fall back to legacy handling
1055                        crate::legacy::struct_datatype(
1056                            crate::legacy::ExportContext {
1057                                cfg: exporter,
1058                                path: vec![],
1059                            },
1060                            parent_name,
1061                            st,
1062                            types,
1063                            s,
1064                            prefix,
1065                            generics,
1066                        )?
1067                    }
1068                }
1069            } else {
1070                // No generics, use legacy path
1071                crate::legacy::struct_datatype(
1072                    crate::legacy::ExportContext {
1073                        cfg: exporter,
1074                        path: vec![],
1075                    },
1076                    parent_name,
1077                    st,
1078                    types,
1079                    s,
1080                    prefix,
1081                    Default::default(),
1082                )?
1083            }
1084        }
1085        DataType::Enum(e) => enum_dt(s, exporter, types, e, location, prefix, generics)?,
1086        DataType::Tuple(t) => tuple_dt(s, exporter, types, t, location, generics)?,
1087        DataType::Reference(r) => {
1088            if let Reference::Named(r) = r
1089                && let Some(ndt) = r.get(types)
1090            {
1091                let combined_generics = merged_generics(generics, r.generics());
1092                inline_datatype(
1093                    s,
1094                    exporter,
1095                    types,
1096                    ndt.ty(),
1097                    location,
1098                    parent_name,
1099                    prefix,
1100                    depth + 1,
1101                    &combined_generics,
1102                )?;
1103            } else {
1104                reference_dt(s, exporter, types, r, location, prefix, generics)?;
1105            }
1106        }
1107    }
1108
1109    Ok(())
1110}
1111
1112pub(crate) fn datatype(
1113    s: &mut String,
1114    exporter: &Exporter,
1115    types: &Types,
1116    dt: &DataType,
1117    location: Vec<Cow<'static, str>>,
1118    parent_name: Option<&str>,
1119    prefix: &str,
1120    generics: &[(GenericReference, DataType)],
1121) -> Result<(), Error> {
1122    // TODO: Validating the variant from `dt` can be flattened
1123
1124    match dt {
1125        DataType::Primitive(p) => s.push_str(primitive_dt(p, location)?),
1126        DataType::List(l) => list_dt(s, exporter, types, l, location, generics)?,
1127        DataType::Map(m) => map_dt(s, exporter, types, m, location, generics)?,
1128        DataType::Nullable(def) => {
1129            // TODO: Replace legacy stuff
1130            let mut inner = String::new();
1131            crate::legacy::datatype_inner(
1132                crate::legacy::ExportContext {
1133                    cfg: exporter,
1134                    path: vec![],
1135                },
1136                def,
1137                types,
1138                &mut inner,
1139                generics,
1140            )?;
1141
1142            s.push_str(&inner);
1143            if inner != "null" && !inner.ends_with(" | null") {
1144                s.push_str(" | null");
1145            }
1146
1147            // datatype(s, ts, types, &*t, location, state)?;
1148            // let or_null = " | null";
1149            // if !s.ends_with(or_null) {
1150            //     s.push_str(or_null);
1151            // }
1152        }
1153        DataType::Struct(st) => {
1154            // location.push(st.name().clone());
1155            // fields_dt(s, ts, types, st.name(), &st.fields(), location, state)?
1156
1157            crate::legacy::struct_datatype(
1158                crate::legacy::ExportContext {
1159                    cfg: exporter,
1160                    path: vec![],
1161                },
1162                parent_name,
1163                st,
1164                types,
1165                s,
1166                prefix,
1167                generics,
1168            )?
1169        }
1170        DataType::Enum(e) => enum_dt(s, exporter, types, e, location, prefix, generics)?,
1171        DataType::Tuple(t) => tuple_dt(s, exporter, types, t, location, generics)?,
1172        DataType::Reference(r) => reference_dt(s, exporter, types, r, location, prefix, generics)?,
1173    };
1174
1175    Ok(())
1176}
1177
1178fn primitive_dt(p: &Primitive, location: Vec<Cow<'static, str>>) -> Result<&'static str, Error> {
1179    use Primitive::*;
1180
1181    Ok(match p {
1182        i8 | i16 | i32 | u8 | u16 | u32 | f16 | f32 | f64 /* this looks wrong but `f64` is the direct equivalent of `number` */ => "number",
1183        usize | isize | i64 | u64 | i128 | u128 | f128 => {
1184            return Err(Error::bigint_forbidden(location.join(".")));
1185        }
1186        Primitive::bool => "boolean",
1187        str | char => "string",
1188    })
1189}
1190
1191fn list_dt(
1192    s: &mut String,
1193    exporter: &Exporter,
1194    types: &Types,
1195    l: &List,
1196    _location: Vec<Cow<'static, str>>,
1197    generics: &[(GenericReference, DataType)],
1198) -> Result<(), Error> {
1199    // TODO: This is the legacy stuff
1200    {
1201        let mut dt = String::new();
1202        crate::legacy::datatype_inner(
1203            crate::legacy::ExportContext {
1204                cfg: exporter,
1205                path: vec![],
1206            },
1207            l.ty(),
1208            types,
1209            &mut dt,
1210            generics,
1211        )?;
1212
1213        let dt = if (dt.contains(' ') && !dt.ends_with('}'))
1214          // This is to do with maintaining order of operations.
1215          // Eg `{} | {}` must be wrapped in parens like `({} | {})[]` but `{}` doesn't cause `{}[]` is valid
1216          || (dt.contains(' ') && (dt.contains('&') || dt.contains('|')))
1217        {
1218            format!("({dt})")
1219        } else {
1220            dt
1221        };
1222
1223        if let Some(length) = l.length() {
1224            s.push('[');
1225
1226            for n in 0..length {
1227                if n != 0 {
1228                    s.push_str(", ");
1229                }
1230
1231                s.push_str(&dt);
1232            }
1233
1234            s.push(']');
1235        } else {
1236            write!(s, "{dt}[]")?;
1237        }
1238    }
1239
1240    //     // We use `T[]` instead of `Array<T>` to avoid issues with circular references.
1241
1242    //     let mut result = String::new();
1243    //     datatype(&mut result, ts, types, &l.ty(), location, state)?;
1244    //     let result = if (result.contains(' ') && !result.ends_with('}'))
1245    //         // This is to do with maintaining order of operations.
1246    //         // Eg `{} | {}` must be wrapped in parens like `({} | {})[]` but `{}` doesn't cause `{}[]` is valid
1247    //         || (result.contains(' ') && (result.contains('&') || result.contains('|')))
1248    //     {
1249    //         format!("({result})")
1250    //     } else {
1251    //         result
1252    //     };
1253
1254    //     match l.length() {
1255    //         Some(len) => {
1256    //             s.push_str("[");
1257    //             iter_with_sep(
1258    //                 s,
1259    //                 0..len,
1260    //                 |s, _| {
1261    //                     s.push_str(&result);
1262    //                     Ok(())
1263    //                 },
1264    //                 ", ",
1265    //             )?;
1266    //             s.push_str("]");
1267    //         }
1268    //         None => {
1269    //             s.push_str(&result);
1270    //             s.push_str("[]");
1271    //         }
1272    //     }
1273
1274    Ok(())
1275}
1276
1277fn map_dt(
1278    s: &mut String,
1279    exporter: &Exporter,
1280    types: &Types,
1281    m: &Map,
1282    location: Vec<Cow<'static, str>>,
1283    generics: &[(GenericReference, DataType)],
1284) -> Result<(), Error> {
1285    let path = map_key_path(&location);
1286    map_keys::validate_map_key(m.key_ty(), types, generics, format!("{path}.<map_key>"))?;
1287
1288    {
1289        fn is_exhaustive(dt: &DataType, types: &Types) -> bool {
1290            match dt {
1291                DataType::Enum(e) => e.variants().iter().filter(|(_, v)| !v.skip()).count() == 0,
1292                DataType::Reference(Reference::Named(r)) => {
1293                    if let Some(ndt) = r.get(types) {
1294                        is_exhaustive(ndt.ty(), types)
1295                    } else {
1296                        false
1297                    }
1298                }
1299                DataType::Reference(Reference::Opaque(_)) => false,
1300                _ => true,
1301            }
1302        }
1303
1304        let resolved_key = map_key_render_type(resolve_generics_in_datatype(m.key_ty(), generics));
1305        let is_exhaustive = is_exhaustive(&resolved_key, types);
1306
1307        // We use `{ [key in K]: V }` instead of `Record<K, V>` to avoid issues with circular references.
1308        // Wrapped in Partial<> because otherwise TypeScript would enforce exhaustiveness.
1309        if !is_exhaustive {
1310            s.push_str("Partial<");
1311        }
1312        s.push_str("{ [key in ");
1313        crate::legacy::datatype_inner(
1314            crate::legacy::ExportContext {
1315                cfg: exporter,
1316                path: vec![],
1317            },
1318            &resolved_key,
1319            types,
1320            s,
1321            generics,
1322        )?;
1323        s.push_str("]: ");
1324        crate::legacy::datatype_inner(
1325            crate::legacy::ExportContext {
1326                cfg: exporter,
1327                path: vec![],
1328            },
1329            m.value_ty(),
1330            types,
1331            s,
1332            generics,
1333        )?;
1334        s.push_str(" }");
1335        if !is_exhaustive {
1336            s.push('>');
1337        }
1338    }
1339    // assert!(flattening, "todo: map flattening");
1340
1341    // // We use `{ [key in K]: V }` instead of `Record<K, V>` to avoid issues with circular references.
1342    // // Wrapped in Partial<> because otherwise TypeScript would enforce exhaustiveness.
1343    // s.push_str("Partial<{ [key in ");
1344    // datatype(s, ts, types, m.key_ty(), location.clone(), state)?;
1345    // s.push_str("]: ");
1346    // datatype(s, ts, types, m.value_ty(), location, state)?;
1347    // s.push_str(" }>");
1348    Ok(())
1349}
1350
1351fn map_key_path(location: &[Cow<'static, str>]) -> String {
1352    if location.is_empty() {
1353        return "HashMap".to_string();
1354    }
1355
1356    location.join(".")
1357}
1358
1359fn map_key_render_type(dt: DataType) -> DataType {
1360    if matches!(dt, DataType::Primitive(Primitive::bool)) {
1361        return bool_key_literal_datatype();
1362    }
1363
1364    dt
1365}
1366
1367fn bool_key_literal_datatype() -> DataType {
1368    let mut bool_enum = Enum::new();
1369    bool_enum
1370        .variants_mut()
1371        .push((Cow::Borrowed("true"), Variant::unit()));
1372    bool_enum
1373        .variants_mut()
1374        .push((Cow::Borrowed("false"), Variant::unit()));
1375    DataType::Enum(bool_enum)
1376}
1377
1378fn enum_dt(
1379    s: &mut String,
1380    exporter: &Exporter,
1381    types: &Types,
1382    e: &Enum,
1383    _location: Vec<Cow<'static, str>>,
1384    prefix: &str,
1385    generics: &[(GenericReference, DataType)],
1386) -> Result<(), Error> {
1387    // TODO: Drop legacy stuff
1388    {
1389        crate::legacy::enum_datatype(
1390            crate::legacy::ExportContext {
1391                cfg: exporter,
1392                path: vec![],
1393            },
1394            e,
1395            types,
1396            s,
1397            prefix,
1398            generics,
1399        )?
1400    }
1401
1402    //     assert!(!state.flattening, "todo: support for flattening enums"); // TODO
1403
1404    //     location.push(e.name().clone());
1405
1406    //     let variants = e.variants().iter().filter(|(_, variant)| !variant.skip());
1407
1408    //     if variants.clone().next().is_none()
1409    //     /* is_empty */
1410    //     {
1411    //         s.push_str("never");
1412    //         return Ok(());
1413    //     }
1414
1415    //     let mut variants = variants
1416    //         .into_iter()
1417    //         .map(|(variant_name, variant)| {
1418    //             let mut s = String::new();
1419    //             let mut location = location.clone();
1420    //             location.push(variant_name.clone());
1421
1422    //             // TODO
1423    //             // variant.deprecated()
1424    //             // variant.docs()
1425
1426    //             match &e.repr() {
1427    //                 EnumRepr::Untagged => {
1428    //                     fields_dt(&mut s, ts, types, variant_name, variant.fields(), location, state)?;
1429    //                 },
1430    //                 EnumRepr::External => match variant.fields() {
1431    //                     Fields::Unit => {
1432    //                         s.push_str("\"");
1433    //                         s.push_str(variant_name);
1434    //                         s.push_str("\"");
1435    //                     },
1436    //                     Fields::Unnamed(n) if n.fields().into_iter().filter(|f| f.ty().is_some()).next().is_none() /* is_empty */ => {
1437    //                         // We detect `#[specta(skip)]` by checking if the unfiltered fields are also empty.
1438    //                         if n.fields().is_empty() {
1439    //                             s.push_str("{ ");
1440    //                             s.push_str(&escape_key(variant_name));
1441    //                             s.push_str(": [] }");
1442    //                         } else {
1443    //                             s.push_str("\"");
1444    //                             s.push_str(variant_name);
1445    //                             s.push_str("\"");
1446    //                         }
1447    //                     }
1448    //                     _ => {
1449    //                         s.push_str("{ ");
1450    //                         s.push_str(&escape_key(variant_name));
1451    //                         s.push_str(": ");
1452    //                         fields_dt(&mut s, ts, types, variant_name, variant.fields(), location, state)?;
1453    //                         s.push_str(" }");
1454    //                     }
1455    //                 }
1456    //                 EnumRepr::Internal { tag } => {
1457    //                     // TODO: Unconditionally wrapping in `(` kinda sucks.
1458    //                     write!(s, "({{ {}: \"{}\"", escape_key(tag), variant_name).expect("infallible");
1459
1460    //                     match variant.fields() {
1461    //                         Fields::Unit => {
1462    //                              s.push_str(" })");
1463    //                         },
1464    //                         // Fields::Unnamed(f) if f.fields.iter().filter(|f| f.ty().is_some()).count() == 1 => {
1465    //                         //     // let mut fields = f.fields().into_iter().filter(|f| f.ty().is_some());
1466
1467    //                         //      s.push_str("______"); // TODO
1468
1469    //                         // //     // if fields.len
1470
1471    //                         // //     // TODO: Having no fields are skipping is valid
1472    //                         // //     // TODO: Having more than 1 field is invalid
1473
1474    //                         // //     // TODO: Check if the field's type is object-like and can be merged.
1475
1476    //                         //     // todo!();
1477    //                         // }
1478    //                         f => {
1479    //                             // TODO: Cleanup and explain this
1480    //                             let mut skip_join = false;
1481    //                             if let Fields::Unnamed(f) = &f {
1482    //                                 let mut fields = f.fields.iter().filter(|f| f.ty().is_some());
1483    //                                 if let (Some(v), None) = (fields.next(), fields.next()) {
1484    //                                     if let Some(DataType::Tuple(tuple)) = &v.ty() {
1485    //                                         skip_join = tuple.elements().len() == 0;
1486    //                                     }
1487    //                                 }
1488    //                             }
1489
1490    //                             if skip_join {
1491    //                                 s.push_str(" })");
1492    //                             } else {
1493    //                                 s.push_str(" } & ");
1494
1495    //                                 // TODO: Can we be smart enough to omit the `{` and `}` if this is an object
1496    //                                 fields_dt(&mut s, ts, types, variant_name, f, location, state)?;
1497    //                                 s.push_str(")");
1498    //                             }
1499
1500    //                             // match f {
1501    //                             //     // Checked above
1502    //                             //     Fields::Unit => unreachable!(),
1503    //                             //     Fields::Unnamed(unnamed_fields) => unnamed_fields,
1504    //                             //     Fields::Named(named_fields) => todo!(),
1505    //                             // }
1506
1507    //                             // println!("{:?}", f); // TODO: If object we can join in fields like this, else `} & ...`
1508    //                             // flattened_fields_dt(&mut s, ts, types, variant_name, f, location, false)?; // TODO: Fix `flattening`
1509
1510    //                         }
1511    //                     }
1512
1513    //                 }
1514    //                 EnumRepr::Adjacent { tag, content } => {
1515    //                     write!(s, "{{ {}: \"{}\"", escape_key(tag), variant_name).expect("infallible");
1516
1517    //                     match variant.fields() {
1518    //                         Fields::Unit => {},
1519    //                         f => {
1520    //                             write!(s, "; {}: ", escape_key(content)).expect("infallible");
1521    //                             fields_dt(&mut s, ts, types, variant_name, f, location, state)?;
1522    //                         }
1523    //                     }
1524
1525    //                     s.push_str(" }");
1526    //                 }
1527    //             }
1528
1529    //             Ok(s)
1530    //         })
1531    //         .collect::<Result<Vec<String>, Error>>()?;
1532
1533    //     // TODO: Instead of deduplicating on the string, we should do it in the AST.
1534    //     // This would avoid the intermediate `String` allocations and be more reliable.
1535    //     variants.dedup();
1536
1537    //     iter_with_sep(
1538    //         s,
1539    //         variants,
1540    //         |s, v| {
1541    //             s.push_str(&v);
1542    //             Ok(())
1543    //         },
1544    //         " | ",
1545    //     )?;
1546
1547    Ok(())
1548}
1549
1550// fn fields_dt(
1551//     s: &mut String,
1552//     ts: &Typescript,
1553//     types: &Types,
1554//     name: &Cow<'static, str>,
1555//     f: &Fields,
1556//     location: Vec<Cow<'static, str>>,
1557//     state: State,
1558// ) -> Result<(), Error> {
1559//     match f {
1560//         Fields::Unit => {
1561//             assert!(!state.flattening, "todo: support for flattening enums"); // TODO
1562//             s.push_str("null")
1563//         }
1564//         Fields::Unnamed(f) => {
1565//             assert!(!state.flattening, "todo: support for flattening enums"); // TODO
1566//             let mut fields = f.fields().into_iter().filter(|f| f.ty().is_some());
1567
1568//             // A single field usually becomes `T`.
1569//             // but when `#[serde(skip)]` is used it should be `[T]`.
1570//             if fields.clone().count() == 1 && f.fields.len() == 1 {
1571//                 return field_dt(
1572//                     s,
1573//                     ts,
1574//                     types,
1575//                     None,
1576//                     fields.next().expect("checked above"),
1577//                     location,
1578//                     state,
1579//                 );
1580//             }
1581
1582//             s.push_str("[");
1583//             iter_with_sep(
1584//                 s,
1585//                 fields.enumerate(),
1586//                 |s, (i, f)| {
1587//                     let mut location = location.clone();
1588//                     location.push(i.to_string().into());
1589
1590//                     field_dt(s, ts, types, None, f, location, state)
1591//                 },
1592//                 ", ",
1593//             )?;
1594//             s.push_str("]");
1595//         }
1596//         Fields::Named(f) => {
1597//             let fields = f.fields().into_iter().filter(|(_, f)| f.ty().is_some());
1598//             if fields.clone().next().is_none()
1599//             /* is_empty */
1600//             {
1601//                 assert!(!state.flattening, "todo: support for flattening enums"); // TODO
1602
1603//                 if let Some(tag) = f.tag() {
1604//                     if !state.flattening {}
1605
1606//                     write!(s, "{{ {}: \"{name}\" }}", escape_key(tag)).expect("infallible");
1607//                 } else {
1608//                     s.push_str("Record<string, never>");
1609//                 }
1610
1611//                 return Ok(());
1612//             }
1613
1614//             if !state.flattening {
1615//                 s.push_str("{ ");
1616//             }
1617//             if let Some(tag) = &f.tag() {
1618//                 write!(s, "{}: \"{name}\"; ", escape_key(tag)).expect("infallible");
1619//             }
1620
1621//             iter_with_sep(
1622//                 s,
1623//                 fields,
1624//                 |s, (key, f)| {
1625//                     let mut location = location.clone();
1626//                     location.push(key.clone());
1627
1628//                     field_dt(s, ts, types, Some(key), f, location, state)
1629//                 },
1630//                 "; ",
1631//             )?;
1632//             if !state.flattening {
1633//                 s.push_str(" }");
1634//             }
1635//         }
1636//     }
1637//     Ok(())
1638// }
1639
1640// // TODO: Remove this to avoid so much duplicate logic
1641// fn flattened_fields_dt(
1642//     s: &mut String,
1643//     ts: &Typescript,
1644//     types: &Types,
1645//     name: &Cow<'static, str>,
1646//     f: &Fields,
1647//     location: Vec<Cow<'static, str>>,
1648//     state: State,
1649// ) -> Result<(), Error> {
1650//     match f {
1651//         Fields::Unit => todo!(), // s.push_str("null"),
1652//         Fields::Unnamed(f) => {
1653//             // TODO: Validate flattening?
1654
1655//             let mut fields = f.fields().into_iter().filter(|f| f.ty().is_some());
1656
1657//             // A single field usually becomes `T`.
1658//             // but when `#[serde(skip)]` is used it should be `[T]`.
1659//             if fields.clone().count() == 1 && f.fields.len() == 1 {
1660//                 return field_dt(
1661//                     s,
1662//                     ts,
1663//                     types,
1664//                     None,
1665//                     fields.next().expect("checked above"),
1666//                     location,
1667//                     state,
1668//                 );
1669//             }
1670
1671//             s.push_str("[");
1672//             iter_with_sep(
1673//                 s,
1674//                 fields.enumerate(),
1675//                 |s, (i, f)| {
1676//                     let mut location = location.clone();
1677//                     location.push(i.to_string().into());
1678
1679//                     field_dt(s, ts, types, None, f, location, state)
1680//                 },
1681//                 ", ",
1682//             )?;
1683//             s.push_str("]");
1684//         }
1685//         Fields::Named(f) => {
1686//             let fields = f.fields().into_iter().filter(|(_, f)| f.ty().is_some());
1687//             if fields.clone().next().is_none()
1688//             /* is_empty */
1689//             {
1690//                 if let Some(tag) = f.tag() {
1691//                     write!(s, "{{ {}: \"{name}\" }}", escape_key(tag)).expect("infallible");
1692//                 } else {
1693//                     s.push_str("Record<string, never>");
1694//                 }
1695
1696//                 return Ok(());
1697//             }
1698
1699//             // s.push_str("{ "); // TODO
1700//             if let Some(tag) = &f.tag() {
1701//                 write!(s, "{}: \"{name}\"; ", escape_key(tag)).expect("infallible");
1702//             }
1703
1704//             iter_with_sep(
1705//                 s,
1706//                 fields,
1707//                 |s, (key, f)| {
1708//                     let mut location = location.clone();
1709//                     location.push(key.clone());
1710
1711//                     field_dt(s, ts, types, Some(key), f, location, state)
1712//                 },
1713//                 "; ",
1714//             )?;
1715//             // s.push_str(" }"); // TODO
1716//         }
1717//     }
1718//     Ok(())
1719// }
1720
1721// fn field_dt(
1722//     s: &mut String,
1723//     ts: &Typescript,
1724//     types: &Types,
1725//     key: Option<&Cow<'static, str>>,
1726//     f: &Field,
1727//     location: Vec<Cow<'static, str>>,
1728//     state: State,
1729// ) -> Result<(), Error> {
1730//     let Some(ty) = f.ty() else {
1731//         // These should be filtered out before getting here.
1732//         unreachable!()
1733//     };
1734
1735//     // TODO
1736//     // field.deprecated(),
1737//     // field.docs(),
1738
1739//     let ty = if f.inline() {
1740//         specta::datatype::inline_dt(types, ty.clone())
1741//     } else {
1742//         ty.clone()
1743//     };
1744
1745//     if !f.flatten() {
1746//         if let Some(key) = key {
1747//             s.push_str(&*escape_key(key));
1748//             // https://github.com/specta-rs/rspc/issues/100#issuecomment-1373092211
1749//             if f.optional() {
1750//                 s.push_str("?");
1751//             }
1752//             s.push_str(": ");
1753//         }
1754//     } else {
1755//         // TODO: We need to validate the inner type can be flattened safely???
1756
1757//         //     data
1758
1759//         //     match ty {
1760//         //         DataType::Any => todo!(),
1761//         //         DataType::Unknown => todo!(),
1762//         //         DataType::Primitive(primitive_type) => todo!(),
1763//         //         DataType::Literal(literal_type) => todo!(),
1764//         //         DataType::List(list) => todo!(),
1765//         //         DataType::Map(map) => todo!(),
1766//         //         DataType::Nullable(data_type) => todo!(),
1767//         //         DataType::Struct(st) => {
1768//         //             // location.push(st.name().clone()); // TODO
1769//         //             flattened_fields_dt(s, ts, types, st.name(), &st.fields(), location)?
1770//         //         }
1771
1772//         //         // flattened_fields_dt(s, ts, types, &ty, location)?,
1773//         //         DataType::Enum(enum_type) => todo!(),
1774//         //         DataType::Tuple(tuple_type) => todo!(),
1775//         //         DataType::Reference(reference) => todo!(),
1776//         //         DataType::Generic(generic_type) => todo!(),
1777//         //     };
1778//     }
1779
1780//     // TODO: Only flatten when object is inline?
1781
1782//     datatype(
1783//         s,
1784//         ts,
1785//         types,
1786//         &ty,
1787//         location,
1788//         State {
1789//             flattening: state.flattening || f.flatten(),
1790//         },
1791//     )?;
1792
1793//     // TODO: This is not always correct but is it ever correct?
1794//     // If we can't use `?` (Eg. in a tuple) we manually join it.
1795//     // if key.is_none() && f.optional() {
1796//     //     s.push_str(" | undefined");
1797//     // }
1798
1799//     Ok(())
1800// }
1801
1802fn tuple_dt(
1803    s: &mut String,
1804    exporter: &Exporter,
1805    types: &Types,
1806    t: &Tuple,
1807    _location: Vec<Cow<'static, str>>,
1808    generics: &[(GenericReference, DataType)],
1809) -> Result<(), Error> {
1810    {
1811        s.push_str(&crate::legacy::tuple_datatype(
1812            crate::legacy::ExportContext {
1813                cfg: exporter,
1814                path: vec![],
1815            },
1816            t,
1817            types,
1818            generics,
1819        )?);
1820    }
1821
1822    // match &t.elements()[..] {
1823    //     [] => s.push_str("null"),
1824    //     elems => {
1825    //         s.push_str("[");
1826    //         iter_with_sep(
1827    //             s,
1828    //             elems.into_iter().enumerate(),
1829    //             |s, (i, dt)| {
1830    //                 let mut location = location.clone();
1831    //                 location.push(i.to_string().into());
1832
1833    //                 datatype(s, ts, types, &dt, location, state)
1834    //             },
1835    //             ", ",
1836    //         )?;
1837    //         s.push_str("]");
1838    //     }
1839    // }
1840    Ok(())
1841}
1842
1843fn reference_dt(
1844    s: &mut String,
1845    exporter: &Exporter,
1846    types: &Types,
1847    r: &Reference,
1848    location: Vec<Cow<'static, str>>,
1849    prefix: &str,
1850    generics: &[(GenericReference, DataType)],
1851) -> Result<(), Error> {
1852    match r {
1853        Reference::Named(r) => {
1854            reference_named_dt(s, exporter, types, r, location, prefix, generics)
1855        }
1856        Reference::Generic(g) => {
1857            if let Some((_, resolved_dt)) = generics.iter().find(|(ge, _)| ge == g) {
1858                if matches!(resolved_dt, DataType::Reference(Reference::Generic(inner)) if inner == g)
1859                {
1860                    write_generic_reference(s, g)?;
1861                    Ok(())
1862                } else {
1863                    datatype(
1864                        s,
1865                        exporter,
1866                        types,
1867                        resolved_dt,
1868                        location,
1869                        None,
1870                        prefix,
1871                        generics,
1872                    )
1873                }
1874            } else {
1875                write_generic_reference(s, g)?;
1876                Ok(())
1877            }
1878        }
1879        Reference::Opaque(r) => reference_opaque_dt(s, exporter, types, r),
1880    }
1881}
1882
1883fn reference_opaque_dt(
1884    s: &mut String,
1885    exporter: &Exporter,
1886    types: &Types,
1887    r: &OpaqueReference,
1888) -> Result<(), Error> {
1889    if let Some(def) = r.downcast_ref::<opaque::Define>() {
1890        s.push_str(&def.0);
1891        return Ok(());
1892    } else if r.downcast_ref::<opaque::Any>().is_some() {
1893        s.push_str("any");
1894        return Ok(());
1895    } else if r.downcast_ref::<opaque::Unknown>().is_some() {
1896        s.push_str("unknown");
1897        return Ok(());
1898    } else if r.downcast_ref::<opaque::Never>().is_some() {
1899        s.push_str("never");
1900        return Ok(());
1901    } else if let Some(def) = r.downcast_ref::<Branded>() {
1902        let resolved_types = ResolvedTypes::from_resolved_types(types.clone());
1903
1904        if let Some(branded_type) = exporter
1905            .branded_type_impl
1906            .as_ref()
1907            .map(|builder| {
1908                (builder.0)(
1909                    BrandedTypeExporter {
1910                        exporter,
1911                        types: &resolved_types,
1912                    },
1913                    def,
1914                )
1915            })
1916            .transpose()?
1917        {
1918            s.push_str(branded_type.as_ref());
1919            return Ok(());
1920        }
1921
1922        // TODO: Build onto `s` instead of appending a separate string
1923        match def.ty() {
1924            DataType::Reference(r) => reference_dt(s, exporter, types, r, vec![], "", &[])?,
1925            ty => inline_datatype(s, exporter, types, ty, vec![], None, "", 0, &[])?,
1926        }
1927        s.push_str(r#" & { { readonly __brand: ""#);
1928        s.push_str(def.brand());
1929        s.push_str("\" }");
1930        return Ok(());
1931    }
1932
1933    Err(Error::unsupported_opaque_reference(r.clone()))
1934}
1935
1936fn reference_named_dt(
1937    s: &mut String,
1938    exporter: &Exporter,
1939    types: &Types,
1940    r: &NamedReference,
1941    location: Vec<Cow<'static, str>>,
1942    prefix: &str,
1943    generics: &[(GenericReference, DataType)],
1944) -> Result<(), Error> {
1945    // TODO: Legacy stuff
1946    {
1947        let ndt = r
1948            .get(types)
1949            .ok_or_else(|| Error::dangling_named_reference(format!("{r:?}")))?;
1950        let _generic_scope = push_generic_scope(ndt.generics());
1951
1952        // Check if this reference should be inlined
1953        if r.inline() {
1954            let inline_key = (
1955                ndt.module_path().clone(),
1956                ndt.name().clone(),
1957                r.generics().to_vec(),
1958            );
1959            let already_inlining = INLINE_REFERENCE_STACK
1960                .with(|stack| stack.borrow().iter().any(|key| key == &inline_key));
1961
1962            if already_inlining {
1963                // Fall through and emit a named reference to break recursive inline expansions.
1964            } else {
1965                INLINE_REFERENCE_STACK.with(|stack| stack.borrow_mut().push(inline_key));
1966                let combined_generics = merged_generics(generics, r.generics());
1967                let resolved = resolve_generics_in_datatype(ndt.ty(), &combined_generics);
1968                let result = datatype(
1969                    s,
1970                    exporter,
1971                    types,
1972                    &resolved,
1973                    location,
1974                    None,
1975                    prefix,
1976                    &combined_generics,
1977                );
1978                INLINE_REFERENCE_STACK.with(|stack| {
1979                    stack.borrow_mut().pop();
1980                });
1981                return result;
1982            }
1983        }
1984
1985        // We check it's valid before tracking
1986        crate::references::track_nr(r);
1987
1988        let name = match exporter.layout {
1989            Layout::ModulePrefixedName => {
1990                let mut s = ndt.module_path().split("::").collect::<Vec<_>>().join("_");
1991                s.push('_');
1992                s.push_str(ndt.name());
1993                Cow::Owned(s)
1994            }
1995            Layout::Namespaces => {
1996                if ndt.module_path().is_empty() {
1997                    ndt.name().clone()
1998                } else {
1999                    let mut path =
2000                        ndt.module_path()
2001                            .split("::")
2002                            .fold("$s$.".to_string(), |mut s, segment| {
2003                                s.push_str(segment);
2004                                s.push('.');
2005                                s
2006                            });
2007                    path.push_str(ndt.name());
2008                    Cow::Owned(path)
2009                }
2010            }
2011            Layout::Files => {
2012                let current_module_path =
2013                    crate::references::current_module_path().unwrap_or_default();
2014
2015                if ndt.module_path() == &current_module_path {
2016                    ndt.name().clone()
2017                } else {
2018                    let mut path = crate::exporter::module_alias(ndt.module_path());
2019                    path.push('.');
2020                    path.push_str(ndt.name());
2021                    Cow::Owned(path)
2022                }
2023            }
2024            _ => ndt.name().clone(),
2025        };
2026
2027        let scoped_generics = generics
2028            .iter()
2029            .filter(|(parent_generic, _)| {
2030                !r.generics()
2031                    .iter()
2032                    .any(|(child_generic, _)| child_generic == parent_generic)
2033            })
2034            .cloned()
2035            .collect::<Vec<_>>();
2036
2037        s.push_str(&name);
2038        if !r.generics().is_empty() {
2039            s.push('<');
2040
2041            for (i, (_, v)) in r.generics().iter().enumerate() {
2042                if i != 0 {
2043                    s.push_str(", ");
2044                }
2045
2046                crate::legacy::datatype_inner(
2047                    crate::legacy::ExportContext {
2048                        cfg: exporter,
2049                        path: vec![],
2050                    },
2051                    v,
2052                    types,
2053                    s,
2054                    &scoped_generics,
2055                )?;
2056            }
2057
2058            s.push('>');
2059        }
2060    }
2061
2062    //     let ndt = types
2063    //         .get(r.sid())
2064    //         // Should be impossible without a bug in Specta.
2065    //         .unwrap_or_else(|| panic!("Missing {:?} in `Types`", r.sid()));
2066
2067    //     if r.inline() {
2068    //         todo!("inline reference!");
2069    //     }
2070
2071    //     s.push_str(ndt.name());
2072    //     // TODO: We could possible break this out, the root `export` function also has to emit generics.
2073    //     match r.generics() {
2074    //         [] => {}
2075    //         generics => {
2076    //             s.push('<');
2077    //             // TODO: Should we push a location for which generic?
2078    //             iter_with_sep(
2079    //                 s,
2080    //                 generics,
2081    //                 |s, dt| datatype(s, ts, types, &dt, location.clone(), state),
2082    //                 ", ",
2083    //             )?;
2084    //             s.push('>');
2085    //         }
2086    //     }
2087
2088    Ok(())
2089}
2090
2091// fn validate_name(
2092//     ident: &Cow<'static, str>,
2093//     location: &Vec<Cow<'static, str>>,
2094// ) -> Result<(), Error> {
2095//     // TODO: Use a perfect hash-map for faster lookups?
2096//     if let Some(name) = RESERVED_TYPE_NAMES.iter().find(|v| **v == ident) {
2097//         return Err(Error::ForbiddenName {
2098//             path: location.join("."),
2099//             name,
2100//         });
2101//     }
2102
2103//     if ident.is_empty() {
2104//         return Err(Error::InvalidName {
2105//             path: location.join("."),
2106//             name: ident.clone(),
2107//         });
2108//     }
2109
2110//     if let Some(first_char) = ident.chars().next() {
2111//         if !first_char.is_alphabetic() && first_char != '_' {
2112//             return Err(Error::InvalidName {
2113//                 path: location.join("."),
2114//                 name: ident.clone(),
2115//             });
2116//         }
2117//     }
2118
2119//     if ident
2120//         .find(|c: char| !c.is_alphanumeric() && c != '_')
2121//         .is_some()
2122//     {
2123//         return Err(Error::InvalidName {
2124//             path: location.join("."),
2125//             name: ident.clone(),
2126//         });
2127//     }
2128
2129//     Ok(())
2130// }
2131
2132// fn escape_key(name: &Cow<'static, str>) -> Cow<'static, str> {
2133//     let needs_escaping = name
2134//         .chars()
2135//         .all(|c| c.is_alphanumeric() || c == '_' || c == '$')
2136//         && name
2137//             .chars()
2138//             .next()
2139//             .map(|first| !first.is_numeric())
2140//             .unwrap_or(true);
2141
2142//     if !needs_escaping {
2143//         format!(r#""{name}""#).into()
2144//     } else {
2145//         name.clone()
2146//     }
2147// }
2148
2149// fn comment() {
2150//     // TODO: Different JSDoc modes
2151
2152//     // TODO: Regular comments
2153//     // TODO: Deprecated
2154
2155//     // TODO: When enabled: arguments, result types
2156// }
2157
2158// /// Iterate with separate and error handling
2159// fn iter_with_sep<T>(
2160//     s: &mut String,
2161//     i: impl IntoIterator<Item = T>,
2162//     mut item: impl FnMut(&mut String, T) -> Result<(), Error>,
2163//     sep: &'static str,
2164// ) -> Result<(), Error> {
2165//     for (i, e) in i.into_iter().enumerate() {
2166//         if i != 0 {
2167//             s.push_str(sep);
2168//         }
2169//         (item)(s, e)?;
2170//     }
2171//     Ok(())
2172// }
2173
2174// A smaller helper until this is stablised into the Rust standard library.
2175fn intersperse<T: Clone>(iter: impl Iterator<Item = T>, sep: T) -> impl Iterator<Item = T> {
2176    iter.enumerate().flat_map(move |(i, item)| {
2177        if i == 0 {
2178            vec![item]
2179        } else {
2180            vec![sep.clone(), item]
2181        }
2182    })
2183}