typescript_type_def/
emit.rs

1use crate::type_expr::{
2    DefinedTypeInfo, Docs, Ident, IndexSignature, NativeTypeInfo, ObjectField,
3    TypeArray, TypeDefinition, TypeExpr, TypeInfo, TypeIntersection, TypeName,
4    TypeObject, TypeString, TypeTuple, TypeUnion,
5};
6use std::{borrow::Cow, io};
7
8/// A Rust type that has a corresponding TypeScript type definition.
9///
10/// For a Rust type `T`, the `TypeDef` trait defines a TypeScript type
11/// which describes JavaScript value that are equivalents of Rust values of
12/// type `T` as encoded to JSON using [`serde_json`](https://docs.rs/serde_json/). The
13/// types are one-to-one, so decoding from TypeScript to JSON to Rust also
14/// works.
15///
16/// ## Implementing
17///
18/// ### Local Types
19///
20/// To derive this trait for your own types, use the
21/// [`#[derive(TypeDef)]`](macro@crate::TypeDef) macro.
22///
23/// ### Foreign Types
24///
25/// To use types from external crates in your own types, the recommended
26/// approach is to create a newtype wrapper and use the `#[type_def(type_of =
27/// "T")]` attribute to specify its type:
28///
29/// ```
30/// use serde::{Deserialize, Serialize};
31/// use typescript_type_def::{write_definition_file, TypeDef};
32///
33/// // The Uuid type from the uuid crate does not implement TypeDef
34/// // But we know that it serializes to just a string
35/// #[derive(
36///     Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, TypeDef,
37/// )]
38/// #[serde(transparent)]
39/// pub struct Uuid(#[type_def(type_of = "String")] pub uuid::Uuid);
40///
41/// // We can now use our newtype in place of the foreign type
42/// #[derive(Debug, Serialize, Deserialize, TypeDef)]
43/// pub struct User {
44///     pub id: Uuid,
45///     pub name: String,
46/// }
47///
48/// let ts_module = {
49///     let mut buf = Vec::new();
50///     write_definition_file::<_, User>(&mut buf, Default::default()).unwrap();
51///     String::from_utf8(buf).unwrap()
52/// };
53/// assert_eq!(
54///     ts_module,
55///     r#"// AUTO-GENERATED by typescript-type-def
56///
57/// export default types;
58/// export namespace types {
59///     export type Uuid = string;
60///     export type User = {
61///         "id": types.Uuid;
62///         "name": string;
63///     };
64/// }
65/// "#
66/// );
67/// ```
68///
69/// The other option if you don't want to create a newtype is to use
70/// `#[type_def(type_of = "T")]` everywhere you use the type:
71///
72/// ```
73/// use serde::{Deserialize, Serialize};
74/// use typescript_type_def::{write_definition_file, TypeDef};
75///
76/// #[derive(Debug, Serialize, Deserialize, TypeDef)]
77/// pub struct User {
78///     #[type_def(type_of = "String")]
79///     pub id: uuid::Uuid,
80///     pub name: String,
81/// }
82///
83/// let ts_module = {
84///     let mut buf = Vec::new();
85///     write_definition_file::<_, User>(&mut buf, Default::default()).unwrap();
86///     String::from_utf8(buf).unwrap()
87/// };
88/// assert_eq!(
89///     ts_module,
90///     r#"// AUTO-GENERATED by typescript-type-def
91///
92/// export default types;
93/// export namespace types {
94///     export type User = {
95///         "id": string;
96///         "name": string;
97///     };
98/// }
99/// "#
100/// );
101/// ```
102///
103/// ### [`std`] Types
104///
105/// [`TypeDef`] is implemented for [`std`] types as follows:
106///
107/// | Rust type | TypeScript type |
108/// |---|---|
109/// | [`bool`] | `boolean` |
110/// | [`String`], [`str`] | `string` |
111/// | [`char`] | `string` |
112/// | [`PathBuf`](std::path::PathBuf), [`Path`](std::path::Path) | `string` |
113/// | [`CString`](std::ffi::CString), [`CStr`](std::ffi::CStr), [`OsString`](std::ffi::OsString), [`OsStr`](std::ffi::OsStr) | `string` |
114/// | [`IpAddr`](std::net::IpAddr), [`Ipv4Addr`](std::net::Ipv4Addr), [`Ipv6Addr`](std::net::Ipv6Addr) | `string` |
115/// | numeric types | `number`[^number] |
116/// | [`()`](unit) | `null` |
117/// | [`(A, B, C)`](tuple) | `[A, B, C]` |
118/// | [`[T; N]`](array) | `[T, T, ..., T]` (an `N`-tuple) |
119// FIXME: https://github.com/rust-lang/rust/issues/86375
120/// | [`Option<T>`] | <code>T \| null</code> |
121/// | [`Vec<T>`], [`[T]`](slice) | `T[]` |
122/// | [`HashSet<T>`](std::collections::HashSet) | `T[]` |
123/// | [`BTreeSet<T>`](std::collections::BTreeSet) | `T[]` |
124/// | [`HashMap<K, V>`](std::collections::HashMap) | `Record<K, V>` |
125/// | [`BTreeMap<K, V>`](std::collections::BTreeMap) | `Record<K, V>` |
126/// | [`&'static T`](reference) | `T` |
127/// | [`Box<T>`] | `T` |
128/// | [`Cow<'static, T>`](std::borrow::Cow) | `T` |
129/// | [`PhantomData<T>`](std::marker::PhantomData) | `T` |
130/// | [`Result<T, E>`](std::result::Result) | <code>{ Ok: T } \| { Err: E }</code> |
131///
132/// ### [`serde_json`] Types
133///
134/// [`TypeDef`] is implemented for types from the [`serde_json`] crate (when the
135/// `json_value` crate feature is enabled) as follows:
136///
137/// | Rust type | TypeScript type |
138/// |---|---|
139/// | [`Value`](serde_json::Value) | <code>null \|<br />boolean \|<br />number \|<br />string \|<br />JSONValue[] \|<br />{ [key: string]: JSONValue; }</code> |
140/// | [`Map<K, V>`](serde_json::Map) | `Record<K, V>` |
141/// | [`Number`](serde_json::Number) | `number` |
142///
143/// [^number]: `std` numeric types are emitted as named aliases converted to
144/// PascalCase (e.g. `Usize`, `I32`, `F64`, `NonZeroI8`, etc.). Since they are
145/// simple aliases, they do not enforce anything in TypeScript about the Rust
146/// types' numeric bounds, but serve to document their intended range.
147pub trait TypeDef: 'static {
148    /// A constant value describing the structure of this type.
149    ///
150    /// This type information is used to emit a TypeScript type definition.
151    const INFO: TypeInfo;
152}
153
154pub(crate) struct EmitCtx<'ctx> {
155    w: &'ctx mut dyn io::Write,
156    root_namespace: Option<&'ctx str>,
157    indent: usize,
158    stats: Stats,
159}
160
161impl EmitCtx<'_> {
162    fn indent(&mut self) {
163        self.indent += 1;
164    }
165
166    fn deindent(&mut self) {
167        debug_assert!(
168            self.indent > 0,
169            "indentation must be > 0 when deindenting"
170        );
171        self.indent -= 1;
172    }
173
174    fn current_indentation(&self) -> Cow<'static, str> {
175        // hard-code common values to avoid frequent string construction
176        match self.indent {
177            0 => "".into(),
178            1 => "    ".into(),
179            2 => "        ".into(),
180            3 => "            ".into(),
181            n => "    ".repeat(n).into(),
182        }
183    }
184}
185
186pub(crate) trait Emit {
187    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()>;
188}
189
190/// Options for customizing the output of [`write_definition_file`].
191///
192/// The default options are:
193/// ```
194/// # use typescript_type_def::DefinitionFileOptions;
195/// # let default =
196/// DefinitionFileOptions {
197///     header: Some("// AUTO-GENERATED by typescript-type-def\n"),
198///     root_namespace: Some("types"),
199/// }
200/// # ;
201/// # assert_eq!(default, Default::default());
202/// ```
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204pub struct DefinitionFileOptions<'a> {
205    /// Text to be emitted at the start of the file.
206    ///
207    /// If `Some`, the string should contain the exact content of the header as
208    /// TypeScript code (usually in the form of comments). If `None`, no header
209    /// will be added.
210    pub header: Option<&'a str>,
211    /// The name of the root namespace which the definitions will be placed
212    /// under.
213    ///
214    /// By default, all exported types are wrapped in a root namespace `types`.
215    /// This gives all types an unambiguous fully-qualified name. When setting
216    /// the `root_namespace` to `None`, no outer namespace is added. This will
217    /// work fine in most situations, but it can however lead to errors in the
218    /// generated TypeScript code when using inner namespaces and types with the
219    /// same name. If you are using inner namespaces through the
220    /// `#[type_def(namespace = "x.y.z")]` attribute, it's recommended to have a
221    /// `root_namespace` as well. See
222    /// [this example](https://www.typescriptlang.org/play?#code/PTAEBUAsEsGdQPYFcAuBTATqFBPADmqJAIbwB2CoGCCKoZxAtmrHsQMZoA0osl0ddsTKIyAGxygARoQxoAZpjkATbJVKgAVklh0ABgDEaegHQBYAFBoAHngQY6uAqCOUAvKADk8mgFpk6BieANyWNnYO9EwsbJyg0GRkmKAA3pagGaAgEDDwCUlYToRw8WSw0MqExFHMrBzcvPwo8EIUZNBCYjXF8Hr5mCaupumZ4faO+ISuoB7efv1BoRaZWWBQJQvYk-HwKBg4CQDmalQKySiUKJCEAckISVwjGdlSqNjXoEhkAI5IxGLQeTQNCqBjMUCGYw7Uq6NDEVQJQJ4OToVQaOSKORkdhHJ6rd6EMQITpbZyQhB6GHoeGIeQEqg0FC+MRoABuaC69zQ5mWo1s41JhAAQsQsB4UqAfAgAFwuGjBUAAXyWisslmyAEF4NU5LAkGIUDwriVDtB2drBaAlPZpGghDpCHoRRgTFLKSRdts+okBkMeBR9EMeSy6FJRbKFiZnTNUpKaLK5gh-KhMJ4lUt1WAAJJ0-6culXQhFbVyT5kSpYHWM7p1Tg8NmYSRFIgaYRlphSaCHJDIeDyfUSXy-f6A4Gg6I8saRMExeqC+BpXkZKcTZzTWZS5OBEJ4lc12LFH1YRcrFZ75vrrybhY7pen7LrPJHy2tT6wIsfL4drs9nTdCHFoMUIXKcmInIWiAplgXI8qefIRKuwqijGEpSrKgGuAqyp4qqFi4ZmOQlAA7vYADW2rwOEdqosGaChqKABM6GTLAJiRtG4pxjKV5+LcQTpkAA)
223    /// of a situation where not having a root namespace can lead to errors.
224    pub root_namespace: Option<&'a str>,
225}
226
227/// Statistics about the type definitions produced by [`write_definition_file`].
228#[derive(Debug, Clone)]
229pub struct Stats {
230    /// The number of unique type definitions produced.
231    pub type_definitions: usize,
232}
233
234impl<'ctx> EmitCtx<'ctx> {
235    fn new(
236        w: &'ctx mut dyn io::Write,
237        root_namespace: Option<&'ctx str>,
238    ) -> Self {
239        let stats = Stats {
240            type_definitions: 0,
241        };
242        Self {
243            w,
244            root_namespace,
245            indent: 0,
246            stats,
247        }
248    }
249}
250
251struct SepList<'a, T>(&'a [T], &'static str);
252
253impl<'a, T> Emit for SepList<'a, T>
254where
255    T: Emit,
256{
257    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
258        let Self(elements, separator) = self;
259        let mut first = true;
260        for element in *elements {
261            if !first {
262                write!(ctx.w, "{}", separator)?;
263            }
264            element.emit(ctx)?;
265            first = false;
266        }
267        Ok(())
268    }
269}
270
271struct Generics<'a, T>(&'a [T]);
272
273impl<'a, T> Emit for Generics<'a, T>
274where
275    T: Emit,
276{
277    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
278        let Self(args) = self;
279        if !args.is_empty() {
280            write!(ctx.w, "<")?;
281            SepList(args, ", ").emit(ctx)?;
282            write!(ctx.w, ">")?;
283        }
284        Ok(())
285    }
286}
287
288impl Emit for TypeExpr {
289    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
290        match self {
291            TypeExpr::Ref(type_info) => ctx.emit_type_ref(type_info),
292            TypeExpr::Name(type_name) => type_name.emit(ctx),
293            TypeExpr::String(type_string) => type_string.emit(ctx),
294            TypeExpr::Tuple(type_tuple) => type_tuple.emit(ctx),
295            TypeExpr::Object(type_object) => type_object.emit(ctx),
296            TypeExpr::Array(type_array) => type_array.emit(ctx),
297            TypeExpr::Union(type_union) => type_union.emit(ctx),
298            TypeExpr::Intersection(type_intersection) => {
299                type_intersection.emit(ctx)
300            }
301        }
302    }
303}
304
305impl Emit for TypeName {
306    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
307        let Self {
308            path,
309            name,
310            generic_args,
311        } = self;
312        for path_part in *path {
313            path_part.emit(ctx)?;
314            write!(ctx.w, ".")?;
315        }
316        name.emit(ctx)?;
317        Generics(generic_args).emit(ctx)?;
318        Ok(())
319    }
320}
321
322impl Emit for TypeString {
323    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
324        let Self { docs, value } = self;
325        docs.emit(ctx)?;
326        write!(ctx.w, "{:?}", value)?;
327        Ok(())
328    }
329}
330
331impl Emit for TypeTuple {
332    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
333        let Self { docs, elements } = self;
334        docs.emit(ctx)?;
335        write!(ctx.w, "[")?;
336        SepList(elements, ", ").emit(ctx)?;
337        write!(ctx.w, "]")?;
338        Ok(())
339    }
340}
341
342impl Emit for TypeObject {
343    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
344        let Self {
345            docs,
346            index_signature,
347            fields,
348        } = self;
349        if let Some(docs) = docs {
350            docs.emit(ctx)?;
351            write!(ctx.w, "{}", ctx.current_indentation())?;
352        }
353        writeln!(ctx.w, "{{")?;
354        ctx.indent();
355        if let Some(IndexSignature { docs, name, value }) = index_signature {
356            docs.emit(ctx)?;
357            write!(ctx.w, "{}[", ctx.current_indentation())?;
358            name.emit(ctx)?;
359            write!(ctx.w, ":string]:")?;
360            value.emit(ctx)?;
361            write!(ctx.w, ";")?;
362            writeln!(ctx.w)?;
363        }
364        for ObjectField {
365            docs,
366            name,
367            optional,
368            r#type,
369        } in *fields
370        {
371            docs.emit(ctx)?;
372            write!(ctx.w, "{}", ctx.current_indentation())?;
373            name.emit(ctx)?;
374            if *optional {
375                write!(ctx.w, "?")?;
376            }
377            write!(ctx.w, ": ")?;
378            r#type.emit(ctx)?;
379            writeln!(ctx.w, ";")?;
380        }
381        ctx.deindent();
382        write!(ctx.w, "{}}}", ctx.current_indentation())?;
383        Ok(())
384    }
385}
386
387impl Emit for TypeArray {
388    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
389        let Self { docs, item } = self;
390        docs.emit(ctx)?;
391        write!(ctx.w, "(")?;
392        item.emit(ctx)?;
393        write!(ctx.w, ")[]")?;
394        Ok(())
395    }
396}
397
398impl Emit for TypeUnion {
399    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
400        let Self { docs, members } = self;
401        docs.emit(ctx)?;
402        if members.is_empty() {
403            write!(ctx.w, "never")?;
404        } else {
405            write!(ctx.w, "(")?;
406            SepList(members, " | ").emit(ctx)?;
407            write!(ctx.w, ")")?;
408        }
409        Ok(())
410    }
411}
412
413impl Emit for TypeIntersection {
414    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
415        let Self { docs, members } = self;
416        docs.emit(ctx)?;
417        if members.is_empty() {
418            write!(ctx.w, "unknown")?;
419        } else {
420            write!(ctx.w, "(")?;
421            SepList(members, " & ").emit(ctx)?;
422            write!(ctx.w, ")")?;
423        }
424        Ok(())
425    }
426}
427
428impl Emit for Ident {
429    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
430        let Self(name) = self;
431        write!(ctx.w, "{}", name)?;
432        Ok(())
433    }
434}
435
436impl Emit for Docs {
437    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
438        let Self(docs) = self;
439        writeln!(ctx.w)?;
440        writeln!(ctx.w, "{}/**", ctx.current_indentation())?;
441        for line in docs.lines() {
442            writeln!(ctx.w, "{} * {}", ctx.current_indentation(), line)?;
443        }
444        writeln!(ctx.w, "{} */", ctx.current_indentation())?;
445        Ok(())
446    }
447}
448
449impl<T> Emit for &T
450where
451    T: Emit,
452{
453    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
454        T::emit(self, ctx)
455    }
456}
457
458impl<T> Emit for Option<T>
459where
460    T: Emit,
461{
462    fn emit(&self, ctx: &mut EmitCtx<'_>) -> io::Result<()> {
463        if let Some(inner) = self {
464            inner.emit(ctx)
465        } else {
466            Ok(())
467        }
468    }
469}
470
471impl EmitCtx<'_> {
472    fn emit_type_def(&mut self, infos: &[&'static TypeInfo]) -> io::Result<()> {
473        for TypeDefinition {
474            docs,
475            path,
476            name,
477            generic_vars,
478            def,
479        } in crate::iter_def_deps::IterDefDeps::new(infos)
480        {
481            self.stats.type_definitions += 1;
482            if !path.is_empty() {
483                write!(
484                    self.w,
485                    "{}export namespace ",
486                    self.current_indentation()
487                )?;
488                SepList(path, ".").emit(self)?;
489                writeln!(self.w, " {{")?;
490                self.indent();
491            }
492            docs.emit(self)?;
493            write!(self.w, "{}export type ", self.current_indentation())?;
494            name.emit(self)?;
495            Generics(generic_vars).emit(self)?;
496            write!(self.w, " = ")?;
497            def.emit(self)?;
498            write!(self.w, ";")?;
499            if !path.is_empty() {
500                writeln!(self.w)?;
501                self.deindent();
502                write!(self.w, "{}}}", self.current_indentation())?;
503            }
504            writeln!(self.w)?;
505        }
506        Ok(())
507    }
508
509    fn emit_type_ref(&mut self, info: &'static TypeInfo) -> io::Result<()> {
510        match info {
511            TypeInfo::Native(NativeTypeInfo { r#ref }) => r#ref.emit(self),
512            TypeInfo::Defined(DefinedTypeInfo {
513                def:
514                    TypeDefinition {
515                        docs: _,
516                        path,
517                        name,
518                        generic_vars: _,
519                        def: _,
520                    },
521                generic_args,
522            }) => {
523                if let Some(root_namespace) = self.root_namespace {
524                    write!(self.w, "{}.", root_namespace)?;
525                }
526                for path_part in *path {
527                    path_part.emit(self)?;
528                    write!(self.w, ".")?;
529                }
530                name.emit(self)?;
531                Generics(generic_args).emit(self)?;
532                Ok(())
533            }
534        }
535    }
536}
537
538impl Default for DefinitionFileOptions<'_> {
539    fn default() -> Self {
540        Self {
541            header: Some("// AUTO-GENERATED by typescript-type-def\n"),
542            root_namespace: Some("types"),
543        }
544    }
545}
546
547/// Writes a TypeScript definition file containing type definitions for `T` to
548/// the given writer.
549///
550/// The resulting TypeScript module will define and export the type definition
551/// for `T` and all of its transitive dependencies under a root namespace. The
552/// name of the root namespace is configurable with the
553/// [`root_namespace`](DefinitionFileOptions::root_namespace) option. Each type
554/// definition may additionally have its own nested namespace under the root
555/// namespace. The root namespace will also be the default export of the module.
556///
557/// If the root namespace is set to `None`, no root namespace and no default
558/// export will be added. See the docs of
559/// [`root_namespace`](DefinitionFileOptions::root_namespace) for an important
560/// note about using no root namespace.
561///
562/// The file will also include a header comment indicating that it was
563/// auto-generated by this library. This is configurable with the
564/// [`header`](DefinitionFileOptions::header) option.
565///
566/// Note that the TypeScript code generated by this library is not very
567/// human-readable. To make the code human-readable, use a TypeScript code
568/// formatter (such as [Prettier](https://prettier.io/)) on the output.
569pub fn write_definition_file<W, T>(
570    writer: W,
571    options: DefinitionFileOptions<'_>,
572) -> io::Result<Stats>
573where
574    W: io::Write,
575    T: ?Sized + TypeDef,
576{
577    write_definition_file_from_type_infos(writer, options, &[&T::INFO])
578}
579
580/// Writes a TypeScript definition file containing type definitions for the
581/// given list of type info values to the given writer.
582///
583/// The type info values can be obtained using [`TypeDef::INFO`] on a type.
584///
585/// The resulting TypeScript module will define and export the type definition
586/// for each given type and all of thair transitive dependencies under a root
587/// namespace. The name of the root namespace is configurable with the
588/// [`root_namespace`](DefinitionFileOptions::root_namespace) option. Each type
589/// definition may additionally have its own nested namespace under the root
590/// namespace. The root namespace will also be the default export of the module.
591///
592/// If the root namespace is set to `None`, no root namespace and no default
593/// export will be added. See the docs of
594/// [`root_namespace`](DefinitionFileOptions::root_namespace) for an important
595/// note about using no root namespace.
596///
597/// The file will also include a header comment indicating that it was
598/// auto-generated by this library. This is configurable with the
599/// [`header`](DefinitionFileOptions::header) option.
600///
601/// Note that the TypeScript code generated by this library is not very
602/// human-readable. To make the code human-readable, use a TypeScript code
603/// formatter (such as [Prettier](https://prettier.io/)) on the output.
604pub fn write_definition_file_from_type_infos<W>(
605    mut writer: W,
606    options: DefinitionFileOptions<'_>,
607    type_infos: &[&'static TypeInfo],
608) -> io::Result<Stats>
609where
610    W: io::Write,
611{
612    let mut ctx = EmitCtx::new(&mut writer, options.root_namespace);
613    if let Some(header) = options.header {
614        writeln!(&mut ctx.w, "{}", header)?;
615    }
616    if let Some(root_namespace) = options.root_namespace {
617        writeln!(&mut ctx.w, "export default {};", root_namespace)?;
618        writeln!(&mut ctx.w, "export namespace {} {{", root_namespace)?;
619        ctx.indent();
620    }
621    ctx.emit_type_def(type_infos)?;
622    if options.root_namespace.is_some() {
623        ctx.deindent();
624        writeln!(&mut ctx.w, "}}")?;
625    }
626    debug_assert_eq!(ctx.indent, 0, "indentation must be 0 after printing");
627    Ok(ctx.stats)
628}
629
630impl TypeInfo {
631    /// Writes a Typescript type expression referencing this type to the given
632    /// writer.
633    ///
634    /// This method is meant to be used in generated code referencing types
635    /// defined in a module created with [`write_definition_file`]. The
636    /// `root_namespace` option should be set to the qualified name of the
637    /// import of that module.
638    ///
639    /// # Example
640    /// ```
641    /// use serde::Serialize;
642    /// use std::io::Write;
643    /// use typescript_type_def::{write_definition_file, TypeDef};
644    ///
645    /// #[derive(Serialize, TypeDef)]
646    /// struct Foo<T> {
647    ///     a: T,
648    /// }
649    ///
650    /// let ts_module = {
651    ///     let mut buf = Vec::new();
652    ///     // types.ts contains type definitions written using write_definition_file
653    ///     writeln!(&mut buf, "import * as types from './types';").unwrap();
654    ///     writeln!(&mut buf).unwrap();
655    ///     write!(&mut buf, "export function myAPI(foo: ").unwrap();
656    ///     let foo_type_info = &<Foo<Vec<u8>> as TypeDef>::INFO;
657    ///     foo_type_info.write_ref_expr(&mut buf, Some("types")).unwrap();
658    ///     writeln!(&mut buf, ") {{}}").unwrap();
659    ///     String::from_utf8(buf).unwrap()
660    /// };
661    /// assert_eq!(
662    ///     ts_module,
663    ///     r#"import * as types from './types';
664    ///
665    /// export function myAPI(foo: types.Foo<(types.U8)[]>) {}
666    /// "#
667    /// );
668    /// ```
669    pub fn write_ref_expr<W>(
670        &'static self,
671        mut writer: W,
672        root_namespace: Option<&str>,
673    ) -> io::Result<()>
674    where
675        W: io::Write,
676    {
677        let mut ctx = EmitCtx::new(&mut writer, root_namespace);
678        ctx.emit_type_ref(self)?;
679        debug_assert_eq!(ctx.indent, 0, "indentation must be 0 after printing");
680        Ok(())
681    }
682}