Skip to main content

tsz_solver/
format.rs

1//! Type formatting for the solver.
2//! Centralizes logic for converting `TypeIds` and `TypeDatas` to human-readable strings.
3
4use crate::TypeDatabase;
5use crate::def::DefinitionStore;
6use crate::diagnostics::{
7    DiagnosticArg, PendingDiagnostic, RelatedInformation, SourceSpan, TypeDiagnostic,
8    get_message_template,
9};
10use crate::types::{
11    CallSignature, CallableShape, ConditionalType, FunctionShape, IntrinsicKind, LiteralValue,
12    MappedType, ObjectShape, ParamInfo, PropertyInfo, StringIntrinsicKind, TemplateSpan,
13    TupleElement, TypeData, TypeId, TypeParamInfo,
14};
15use rustc_hash::FxHashMap;
16use std::sync::Arc;
17use tracing::trace;
18use tsz_binder::SymbolId;
19use tsz_common::interner::Atom;
20
21/// Context for generating type strings.
22pub struct TypeFormatter<'a> {
23    interner: &'a dyn TypeDatabase,
24    /// Symbol arena for looking up symbol names (optional)
25    symbol_arena: Option<&'a tsz_binder::SymbolArena>,
26    /// Definition store for looking up `DefId` names (optional)
27    def_store: Option<&'a DefinitionStore>,
28    /// Maximum depth for nested type printing
29    max_depth: u32,
30    /// Maximum number of union members to display before truncating
31    max_union_members: usize,
32    /// Current depth
33    current_depth: u32,
34    atom_cache: FxHashMap<Atom, Arc<str>>,
35}
36
37impl<'a> TypeFormatter<'a> {
38    pub fn new(interner: &'a dyn TypeDatabase) -> Self {
39        TypeFormatter {
40            interner,
41            symbol_arena: None,
42            def_store: None,
43            max_depth: 5,
44            max_union_members: 5,
45            current_depth: 0,
46            atom_cache: FxHashMap::default(),
47        }
48    }
49
50    /// Create a formatter with access to symbol names.
51    pub fn with_symbols(
52        interner: &'a dyn TypeDatabase,
53        symbol_arena: &'a tsz_binder::SymbolArena,
54    ) -> Self {
55        TypeFormatter {
56            interner,
57            symbol_arena: Some(symbol_arena),
58            def_store: None,
59            max_depth: 5,
60            max_union_members: 5,
61            current_depth: 0,
62            atom_cache: FxHashMap::default(),
63        }
64    }
65
66    /// Add access to definition store for `DefId` name resolution.
67    pub const fn with_def_store(mut self, def_store: &'a DefinitionStore) -> Self {
68        self.def_store = Some(def_store);
69        self
70    }
71
72    pub const fn with_limits(mut self, max_depth: u32, max_union_members: usize) -> Self {
73        self.max_depth = max_depth;
74        self.max_union_members = max_union_members;
75        self
76    }
77
78    fn atom(&mut self, atom: Atom) -> Arc<str> {
79        if let Some(value) = self.atom_cache.get(&atom) {
80            return std::sync::Arc::clone(value);
81        }
82        let resolved = self.interner.resolve_atom_ref(atom);
83        self.atom_cache
84            .insert(atom, std::sync::Arc::clone(&resolved));
85        resolved
86    }
87
88    /// Render a pending diagnostic to a complete diagnostic with formatted message.
89    ///
90    /// This is where the lazy evaluation happens - we format types to strings
91    /// only when the diagnostic is actually going to be displayed.
92    pub fn render(&mut self, pending: &PendingDiagnostic) -> TypeDiagnostic {
93        let template = get_message_template(pending.code);
94        let message = self.render_template(template, &pending.args);
95
96        let mut diag = TypeDiagnostic {
97            message,
98            code: pending.code,
99            severity: pending.severity,
100            span: pending.span.clone(),
101            related: Vec::new(),
102        };
103
104        // Render related diagnostics, falling back to the primary span.
105        let fallback_span = pending
106            .span
107            .clone()
108            .unwrap_or_else(|| SourceSpan::new("<unknown>", 0, 0));
109        for related in &pending.related {
110            let related_msg =
111                self.render_template(get_message_template(related.code), &related.args);
112            let span = related
113                .span
114                .clone()
115                .unwrap_or_else(|| fallback_span.clone());
116            diag.related.push(RelatedInformation {
117                span,
118                message: related_msg,
119            });
120        }
121
122        diag
123    }
124
125    /// Render a message template with arguments.
126    fn render_template(&mut self, template: &str, args: &[DiagnosticArg]) -> String {
127        let mut result = template.to_string();
128
129        for (i, arg) in args.iter().enumerate() {
130            let placeholder = format!("{{{i}}}");
131            if !template.contains(&placeholder) {
132                continue;
133            }
134            let replacement = match arg {
135                DiagnosticArg::Type(type_id) => self.format(*type_id),
136                DiagnosticArg::Symbol(sym_id) => {
137                    if let Some(arena) = self.symbol_arena {
138                        if let Some(sym) = arena.get(*sym_id) {
139                            sym.escaped_name.to_string()
140                        } else {
141                            format!("Symbol({})", sym_id.0)
142                        }
143                    } else {
144                        format!("Symbol({})", sym_id.0)
145                    }
146                }
147                DiagnosticArg::Atom(atom) => self.atom(*atom).to_string(),
148                DiagnosticArg::String(s) => s.to_string(),
149                DiagnosticArg::Number(n) => n.to_string(),
150            };
151            result = result.replace(&placeholder, &replacement);
152        }
153
154        result
155    }
156
157    /// Format a type as a human-readable string.
158    pub fn format(&mut self, type_id: TypeId) -> String {
159        if self.current_depth >= self.max_depth {
160            return "...".to_string();
161        }
162
163        // Handle intrinsic types
164        match type_id {
165            TypeId::NEVER => return "never".to_string(),
166            TypeId::UNKNOWN => return "unknown".to_string(),
167            TypeId::ANY => return "any".to_string(),
168            TypeId::VOID => return "void".to_string(),
169            TypeId::UNDEFINED => return "undefined".to_string(),
170            TypeId::NULL => return "null".to_string(),
171            TypeId::BOOLEAN => return "boolean".to_string(),
172            TypeId::NUMBER => return "number".to_string(),
173            TypeId::STRING => return "string".to_string(),
174            TypeId::BIGINT => return "bigint".to_string(),
175            TypeId::SYMBOL => return "symbol".to_string(),
176            TypeId::OBJECT => return "object".to_string(),
177            TypeId::FUNCTION => return "Function".to_string(),
178            TypeId::ERROR => return "error".to_string(),
179            _ => {}
180        }
181
182        let key = match self.interner.lookup(type_id) {
183            Some(k) => k,
184            None => return format!("Type({})", type_id.0),
185        };
186
187        self.current_depth += 1;
188        let result = self.format_key(&key);
189        self.current_depth -= 1;
190        result
191    }
192
193    fn format_key(&mut self, key: &TypeData) -> String {
194        match key {
195            TypeData::Intrinsic(kind) => self.format_intrinsic(*kind),
196            TypeData::Literal(lit) => self.format_literal(lit),
197            TypeData::Object(shape_id) => {
198                let shape = self.interner.object_shape(*shape_id);
199
200                // First, check if this is a class instance type with a symbol
201                // Class instance types have their symbol set for nominal typing
202                if let Some(sym_id) = shape.symbol
203                    && let Some(arena) = self.symbol_arena
204                    && let Some(sym) = arena.get(sym_id)
205                {
206                    // Use the class name instead of expanding all properties
207                    return sym.escaped_name.to_string();
208                }
209
210                // If not a class or symbol not available, try definition store
211                if let Some(def_store) = self.def_store
212                    && let Some(def_id) = def_store.find_def_by_shape(&shape)
213                    && let Some(def) = def_store.get(def_id)
214                {
215                    // Use the definition name if available
216                    return self.atom(def.name).to_string();
217                }
218                self.format_object(shape.properties.as_slice())
219            }
220            TypeData::ObjectWithIndex(shape_id) => {
221                let shape = self.interner.object_shape(*shape_id);
222
223                // First, check if this is a class instance type with a symbol
224                // Class instance types have their symbol set for nominal typing
225                if let Some(sym_id) = shape.symbol
226                    && let Some(arena) = self.symbol_arena
227                    && let Some(sym) = arena.get(sym_id)
228                {
229                    // Use the class name instead of expanding all properties
230                    return sym.escaped_name.to_string();
231                }
232
233                // If not a class or symbol not available, try definition store
234                if let Some(def_store) = self.def_store
235                    && let Some(def_id) = def_store.find_def_by_shape(&shape)
236                    && let Some(def) = def_store.get(def_id)
237                {
238                    // Use the definition name if available
239                    return self.atom(def.name).to_string();
240                }
241                self.format_object_with_index(shape.as_ref())
242            }
243            TypeData::Union(members) => {
244                let members = self.interner.type_list(*members);
245                self.format_union(members.as_ref())
246            }
247            TypeData::Intersection(members) => {
248                let members = self.interner.type_list(*members);
249                self.format_intersection(members.as_ref())
250            }
251            TypeData::Array(elem) => format!("{}[]", self.format(*elem)),
252            TypeData::Tuple(elements) => {
253                let elements = self.interner.tuple_list(*elements);
254                self.format_tuple(elements.as_ref())
255            }
256            TypeData::Function(shape_id) => {
257                let shape = self.interner.function_shape(*shape_id);
258                self.format_function(shape.as_ref())
259            }
260            TypeData::Callable(shape_id) => {
261                let shape = self.interner.callable_shape(*shape_id);
262                self.format_callable(shape.as_ref())
263            }
264            TypeData::TypeParameter(info) => self.atom(info.name).to_string(),
265            TypeData::Lazy(def_id) => {
266                // Try to get the type name from the definition store
267                if let Some(def_store) = self.def_store {
268                    if let Some(def) = def_store.get(*def_id) {
269                        // Use the definition name if available
270                        self.atom(def.name).to_string()
271                    } else {
272                        format!("Lazy({})", def_id.0)
273                    }
274                } else {
275                    format!("Lazy({})", def_id.0)
276                }
277            }
278            TypeData::Recursive(idx) => {
279                format!("Recursive({idx})")
280            }
281            TypeData::BoundParameter(idx) => {
282                format!("BoundParameter({idx})")
283            }
284            TypeData::Application(app) => {
285                let app = self.interner.type_application(*app);
286                let base_key = self.interner.lookup(app.base);
287
288                trace!(
289                    base_type_id = %app.base.0,
290                    ?base_key,
291                    args_count = app.args.len(),
292                    "Formatting Application"
293                );
294
295                // Special handling for Application(Lazy(def_id), args)
296                // Format as "TypeName<Args>" instead of "Lazy(def_id)<Args>"
297                let base_str = if let Some(TypeData::Lazy(def_id)) = base_key {
298                    if let Some(def_store) = self.def_store {
299                        if let Some(def) = def_store.get(def_id) {
300                            let name = self.atom(def.name).to_string();
301                            trace!(
302                                def_id = %def_id.0,
303                                name = %name,
304                                kind = ?def.kind,
305                                type_params_count = def.type_params.len(),
306                                "Application base resolved from DefId"
307                            );
308                            name
309                        } else {
310                            trace!(def_id = %def_id.0, "DefId not found in store");
311                            format!("Lazy({})", def_id.0)
312                        }
313                    } else {
314                        trace!(def_id = %def_id.0, "No def_store available");
315                        format!("Lazy({})", def_id.0)
316                    }
317                } else {
318                    let formatted = self.format(app.base);
319                    trace!(
320                        base_formatted = %formatted,
321                        "Application base formatted (not Lazy)"
322                    );
323                    formatted
324                };
325
326                let args: Vec<String> = app.args.iter().map(|&arg| self.format(arg)).collect();
327                let result = format!("{}<{}>", base_str, args.join(", "));
328                trace!(result = %result, "Application formatted");
329                result
330            }
331            TypeData::Conditional(cond_id) => {
332                let cond = self.interner.conditional_type(*cond_id);
333                self.format_conditional(cond.as_ref())
334            }
335            TypeData::Mapped(mapped_id) => {
336                let mapped = self.interner.mapped_type(*mapped_id);
337                self.format_mapped(mapped.as_ref())
338            }
339            TypeData::IndexAccess(obj, idx) => {
340                format!("{}[{}]", self.format(*obj), self.format(*idx))
341            }
342            TypeData::TemplateLiteral(spans) => {
343                let spans = self.interner.template_list(*spans);
344                self.format_template_literal(spans.as_ref())
345            }
346            TypeData::TypeQuery(sym) => {
347                let name = if let Some(arena) = self.symbol_arena {
348                    if let Some(symbol) = arena.get(SymbolId(sym.0)) {
349                        symbol.escaped_name.to_string()
350                    } else {
351                        format!("Ref({})", sym.0)
352                    }
353                } else {
354                    format!("Ref({})", sym.0)
355                };
356                format!("typeof {name}")
357            }
358            TypeData::KeyOf(operand) => format!("keyof {}", self.format(*operand)),
359            TypeData::ReadonlyType(inner) => format!("readonly {}", self.format(*inner)),
360            TypeData::NoInfer(inner) => format!("NoInfer<{}>", self.format(*inner)),
361            TypeData::UniqueSymbol(sym) => {
362                let name = if let Some(arena) = self.symbol_arena {
363                    if let Some(symbol) = arena.get(SymbolId(sym.0)) {
364                        symbol.escaped_name.to_string()
365                    } else {
366                        format!("symbol({})", sym.0)
367                    }
368                } else {
369                    format!("symbol({})", sym.0)
370                };
371                format!("unique symbol {name}")
372            }
373            TypeData::Infer(info) => format!("infer {}", self.atom(info.name)),
374            TypeData::ThisType => "this".to_string(),
375            TypeData::StringIntrinsic { kind, type_arg } => {
376                let kind_name = match kind {
377                    StringIntrinsicKind::Uppercase => "Uppercase",
378                    StringIntrinsicKind::Lowercase => "Lowercase",
379                    StringIntrinsicKind::Capitalize => "Capitalize",
380                    StringIntrinsicKind::Uncapitalize => "Uncapitalize",
381                };
382                format!("{}<{}>", kind_name, self.format(*type_arg))
383            }
384            TypeData::Enum(def_id, _member_type) => {
385                // Try to get the enum name from the definition store
386                if let Some(def_store) = self.def_store {
387                    if let Some(def) = def_store.get(*def_id) {
388                        // Use the definition name if available
389                        self.atom(def.name).to_string()
390                    } else {
391                        format!("Enum({})", def_id.0)
392                    }
393                } else {
394                    format!("Enum({})", def_id.0)
395                }
396            }
397            TypeData::ModuleNamespace(sym) => {
398                let name = if let Some(arena) = self.symbol_arena {
399                    if let Some(symbol) = arena.get(SymbolId(sym.0)) {
400                        symbol.escaped_name.to_string()
401                    } else {
402                        format!("module({})", sym.0)
403                    }
404                } else {
405                    format!("module({})", sym.0)
406                };
407                format!("typeof import(\"{name}\")")
408            }
409            TypeData::Error => "error".to_string(),
410        }
411    }
412
413    fn format_intrinsic(&self, kind: IntrinsicKind) -> String {
414        match kind {
415            IntrinsicKind::Any => "any",
416            IntrinsicKind::Unknown => "unknown",
417            IntrinsicKind::Never => "never",
418            IntrinsicKind::Void => "void",
419            IntrinsicKind::Null => "null",
420            IntrinsicKind::Undefined => "undefined",
421            IntrinsicKind::Boolean => "boolean",
422            IntrinsicKind::Number => "number",
423            IntrinsicKind::String => "string",
424            IntrinsicKind::Bigint => "bigint",
425            IntrinsicKind::Symbol => "symbol",
426            IntrinsicKind::Object => "object",
427            IntrinsicKind::Function => "Function",
428        }
429        .to_string()
430    }
431
432    fn format_literal(&mut self, lit: &LiteralValue) -> String {
433        match lit {
434            LiteralValue::String(s) => format!("\"{}\"", self.atom(*s)),
435            LiteralValue::Number(n) => format!("{}", n.0),
436            LiteralValue::BigInt(b) => format!("{}n", self.atom(*b)),
437            LiteralValue::Boolean(b) => if *b { "true" } else { "false" }.to_string(),
438        }
439    }
440
441    fn format_object(&mut self, props: &[PropertyInfo]) -> String {
442        if props.is_empty() {
443            return "{}".to_string();
444        }
445        let mut sorted_props: Vec<&PropertyInfo> = props.iter().collect();
446        sorted_props.sort_by(|a, b| {
447            self.interner
448                .resolve_atom_ref(a.name)
449                .cmp(&self.interner.resolve_atom_ref(b.name))
450        });
451        if props.len() > 3 {
452            let first_three: Vec<String> = sorted_props
453                .iter()
454                .take(3)
455                .map(|p| self.format_property(p))
456                .collect();
457            return format!("{{ {}; ...; }}", first_three.join("; "));
458        }
459        let formatted: Vec<String> = sorted_props
460            .iter()
461            .map(|p| self.format_property(p))
462            .collect();
463        format!("{{ {}; }}", formatted.join("; "))
464    }
465
466    fn format_property(&mut self, prop: &PropertyInfo) -> String {
467        let optional = if prop.optional { "?" } else { "" };
468        let readonly = if prop.readonly { "readonly " } else { "" };
469        let type_str = self.format(prop.type_id);
470        let name = self.atom(prop.name);
471        format!("{readonly}{name}{optional}: {type_str}")
472    }
473
474    fn format_type_params(&mut self, type_params: &[TypeParamInfo]) -> String {
475        if type_params.is_empty() {
476            return String::new();
477        }
478
479        let mut parts = Vec::with_capacity(type_params.len());
480        for tp in type_params {
481            let mut part = String::new();
482            if tp.is_const {
483                part.push_str("const ");
484            }
485            part.push_str(self.atom(tp.name).as_ref());
486            if let Some(constraint) = tp.constraint {
487                part.push_str(" extends ");
488                part.push_str(&self.format(constraint));
489            }
490            if let Some(default) = tp.default {
491                part.push_str(" = ");
492                part.push_str(&self.format(default));
493            }
494            parts.push(part);
495        }
496
497        format!("<{}>", parts.join(", "))
498    }
499
500    fn format_params(&mut self, params: &[ParamInfo], this_type: Option<TypeId>) -> Vec<String> {
501        let mut rendered = Vec::with_capacity(params.len() + usize::from(this_type.is_some()));
502
503        if let Some(this_ty) = this_type {
504            rendered.push(format!("this: {}", self.format(this_ty)));
505        }
506
507        for p in params {
508            let name = p
509                .name
510                .map_or_else(|| "_".to_string(), |atom| self.atom(atom).to_string());
511            let optional = if p.optional { "?" } else { "" };
512            let rest = if p.rest { "..." } else { "" };
513            let type_str = self.format(p.type_id);
514            rendered.push(format!("{rest}{name}{optional}: {type_str}"));
515        }
516
517        rendered
518    }
519
520    fn format_signature_arrow(
521        &mut self,
522        type_params: &[TypeParamInfo],
523        params: &[ParamInfo],
524        this_type: Option<TypeId>,
525        return_type: TypeId,
526        is_construct: bool,
527    ) -> String {
528        let prefix = if is_construct { "new " } else { "" };
529        let type_params = self.format_type_params(type_params);
530        let params = self.format_params(params, this_type);
531        let return_str = if is_construct && return_type == TypeId::UNKNOWN {
532            "any".to_string()
533        } else {
534            self.format(return_type)
535        };
536        format!(
537            "{}{}({}) => {}",
538            prefix,
539            type_params,
540            params.join(", "),
541            return_str
542        )
543    }
544
545    fn format_object_with_index(&mut self, shape: &ObjectShape) -> String {
546        let mut parts = Vec::new();
547
548        if let Some(ref idx) = shape.string_index {
549            parts.push(format!("[index: string]: {}", self.format(idx.value_type)));
550        }
551        if let Some(ref idx) = shape.number_index {
552            parts.push(format!("[index: number]: {}", self.format(idx.value_type)));
553        }
554        let mut sorted_props: Vec<&PropertyInfo> = shape.properties.iter().collect();
555        sorted_props.sort_by(|a, b| {
556            self.interner
557                .resolve_atom_ref(a.name)
558                .cmp(&self.interner.resolve_atom_ref(b.name))
559        });
560        for prop in sorted_props {
561            parts.push(self.format_property(prop));
562        }
563
564        format!("{{ {}; }}", parts.join("; "))
565    }
566
567    fn format_union(&mut self, members: &[TypeId]) -> String {
568        if members.len() > self.max_union_members {
569            let first: Vec<String> = members
570                .iter()
571                .take(self.max_union_members)
572                .map(|&m| self.format(m))
573                .collect();
574            return format!("{} | ...", first.join(" | "));
575        }
576        let formatted: Vec<String> = members.iter().map(|&m| self.format(m)).collect();
577        formatted.join(" | ")
578    }
579
580    fn format_intersection(&mut self, members: &[TypeId]) -> String {
581        let formatted: Vec<String> = members.iter().map(|&m| self.format(m)).collect();
582        formatted.join(" & ")
583    }
584
585    fn format_tuple(&mut self, elements: &[TupleElement]) -> String {
586        let formatted: Vec<String> = elements
587            .iter()
588            .map(|e| {
589                let rest = if e.rest { "..." } else { "" };
590                let optional = if e.optional { "?" } else { "" };
591                let type_str = self.format(e.type_id);
592                if let Some(name_atom) = e.name {
593                    let name = self.atom(name_atom);
594                    format!("{name}{optional}: {rest}{type_str}")
595                } else {
596                    format!("{rest}{type_str}{optional}")
597                }
598            })
599            .collect();
600        format!("[{}]", formatted.join(", "))
601    }
602
603    fn format_function(&mut self, shape: &FunctionShape) -> String {
604        self.format_signature_arrow(
605            &shape.type_params,
606            &shape.params,
607            shape.this_type,
608            shape.return_type,
609            shape.is_constructor,
610        )
611    }
612
613    fn format_callable(&mut self, shape: &CallableShape) -> String {
614        if !shape.construct_signatures.is_empty()
615            && let Some(sym_id) = shape.symbol
616            && let Some(arena) = self.symbol_arena
617            && let Some(sym) = arena.get(sym_id)
618        {
619            return format!("typeof {}", sym.escaped_name);
620        }
621
622        let has_index = shape.string_index.is_some() || shape.number_index.is_some();
623        if !has_index && shape.properties.is_empty() {
624            if shape.call_signatures.len() == 1 && shape.construct_signatures.is_empty() {
625                let sig = &shape.call_signatures[0];
626                return self.format_signature_arrow(
627                    &sig.type_params,
628                    &sig.params,
629                    sig.this_type,
630                    sig.return_type,
631                    false,
632                );
633            }
634            if shape.construct_signatures.len() == 1 && shape.call_signatures.is_empty() {
635                let sig = &shape.construct_signatures[0];
636                return self.format_signature_arrow(
637                    &sig.type_params,
638                    &sig.params,
639                    sig.this_type,
640                    sig.return_type,
641                    true,
642                );
643            }
644        }
645
646        let mut parts = Vec::new();
647        for sig in &shape.call_signatures {
648            parts.push(self.format_call_signature(sig, false));
649        }
650        for sig in &shape.construct_signatures {
651            parts.push(self.format_call_signature(sig, true));
652        }
653        if let Some(ref idx) = shape.string_index {
654            parts.push(format!("[index: string]: {}", self.format(idx.value_type)));
655        }
656        if let Some(ref idx) = shape.number_index {
657            parts.push(format!("[index: number]: {}", self.format(idx.value_type)));
658        }
659        let mut sorted_props: Vec<&PropertyInfo> = shape.properties.iter().collect();
660        sorted_props.sort_by(|a, b| {
661            self.interner
662                .resolve_atom_ref(a.name)
663                .cmp(&self.interner.resolve_atom_ref(b.name))
664        });
665        for prop in sorted_props {
666            parts.push(self.format_property(prop));
667        }
668
669        if parts.is_empty() {
670            return "{}".to_string();
671        }
672
673        format!("{{ {}; }}", parts.join("; "))
674    }
675
676    fn format_call_signature(&mut self, sig: &CallSignature, is_construct: bool) -> String {
677        let prefix = if is_construct { "new " } else { "" };
678        let type_params = self.format_type_params(&sig.type_params);
679        let params = self.format_params(&sig.params, sig.this_type);
680        let return_str = if is_construct && sig.return_type == TypeId::UNKNOWN {
681            "any".to_string()
682        } else {
683            self.format(sig.return_type)
684        };
685        format!(
686            "{}{}({}): {}",
687            prefix,
688            type_params,
689            params.join(", "),
690            return_str
691        )
692    }
693
694    fn format_conditional(&mut self, cond: &ConditionalType) -> String {
695        format!(
696            "{} extends {} ? {} : {}",
697            self.format(cond.check_type),
698            self.format(cond.extends_type),
699            self.format(cond.true_type),
700            self.format(cond.false_type)
701        )
702    }
703
704    fn format_mapped(&mut self, mapped: &MappedType) -> String {
705        format!(
706            "{{ [K in {}]: {} }}",
707            self.format(mapped.constraint),
708            self.format(mapped.template)
709        )
710    }
711
712    fn format_template_literal(&mut self, spans: &[TemplateSpan]) -> String {
713        let mut result = String::from("`");
714        for span in spans {
715            match span {
716                TemplateSpan::Text(text) => {
717                    let text = self.atom(*text);
718                    result.push_str(text.as_ref());
719                }
720                TemplateSpan::Type(type_id) => {
721                    result.push_str("${");
722                    result.push_str(&self.format(*type_id));
723                    result.push('}');
724                }
725            }
726        }
727        result.push('`');
728        result
729    }
730}