1use 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
21pub struct TypeFormatter<'a> {
23 interner: &'a dyn TypeDatabase,
24 symbol_arena: Option<&'a tsz_binder::SymbolArena>,
26 def_store: Option<&'a DefinitionStore>,
28 max_depth: u32,
30 max_union_members: usize,
32 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 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 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 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 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 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 pub fn format(&mut self, type_id: TypeId) -> String {
159 if self.current_depth >= self.max_depth {
160 return "...".to_string();
161 }
162
163 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 if let Some(sym_id) = shape.symbol
203 && let Some(arena) = self.symbol_arena
204 && let Some(sym) = arena.get(sym_id)
205 {
206 return sym.escaped_name.to_string();
208 }
209
210 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 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 if let Some(sym_id) = shape.symbol
226 && let Some(arena) = self.symbol_arena
227 && let Some(sym) = arena.get(sym_id)
228 {
229 return sym.escaped_name.to_string();
231 }
232
233 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 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 if let Some(def_store) = self.def_store {
268 if let Some(def) = def_store.get(*def_id) {
269 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 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 if let Some(def_store) = self.def_store {
387 if let Some(def) = def_store.get(*def_id) {
388 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}