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}