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(name) = self.format_symbol_name(*sym_id) {
138 name
139 } else {
140 format!("Symbol({})", sym_id.0)
141 }
142 }
143 DiagnosticArg::Atom(atom) => self.atom(*atom).to_string(),
144 DiagnosticArg::String(s) => s.to_string(),
145 DiagnosticArg::Number(n) => n.to_string(),
146 };
147 result = result.replace(&placeholder, &replacement);
148 }
149
150 result
151 }
152
153 pub fn format(&mut self, type_id: TypeId) -> String {
155 if self.current_depth >= self.max_depth {
156 return "...".to_string();
157 }
158
159 match type_id {
161 TypeId::NEVER => return "never".to_string(),
162 TypeId::UNKNOWN => return "unknown".to_string(),
163 TypeId::ANY => return "any".to_string(),
164 TypeId::VOID => return "void".to_string(),
165 TypeId::UNDEFINED => return "undefined".to_string(),
166 TypeId::NULL => return "null".to_string(),
167 TypeId::BOOLEAN => return "boolean".to_string(),
168 TypeId::NUMBER => return "number".to_string(),
169 TypeId::STRING => return "string".to_string(),
170 TypeId::BIGINT => return "bigint".to_string(),
171 TypeId::SYMBOL => return "symbol".to_string(),
172 TypeId::OBJECT => return "object".to_string(),
173 TypeId::FUNCTION => return "Function".to_string(),
174 TypeId::ERROR => return "error".to_string(),
175 _ => {}
176 }
177
178 let key = match self.interner.lookup(type_id) {
179 Some(k) => k,
180 None => return format!("Type({})", type_id.0),
181 };
182
183 self.current_depth += 1;
184 let result = self.format_key(&key);
185 self.current_depth -= 1;
186 result
187 }
188
189 fn format_key(&mut self, key: &TypeData) -> String {
190 match key {
191 TypeData::Intrinsic(kind) => self.format_intrinsic(*kind),
192 TypeData::Literal(lit) => self.format_literal(lit),
193 TypeData::Object(shape_id) => {
194 let shape = self.interner.object_shape(*shape_id);
195 if let Some(name) = self.resolve_object_shape_name(&shape) {
196 return name;
197 }
198 self.format_object(shape.properties.as_slice())
199 }
200 TypeData::ObjectWithIndex(shape_id) => {
201 let shape = self.interner.object_shape(*shape_id);
202 if let Some(name) = self.resolve_object_shape_name(&shape) {
203 return name;
204 }
205 self.format_object_with_index(shape.as_ref())
206 }
207 TypeData::Union(members) => {
208 let members = self.interner.type_list(*members);
209 self.format_union(members.as_ref())
210 }
211 TypeData::Intersection(members) => {
212 let members = self.interner.type_list(*members);
213 self.format_intersection(members.as_ref())
214 }
215 TypeData::Array(elem) => {
216 let elem_formatted = self.format(*elem);
217 let needs_parens = matches!(
218 self.interner.lookup(*elem),
219 Some(
220 TypeData::Union(_)
221 | TypeData::Intersection(_)
222 | TypeData::Function(_)
223 | TypeData::Callable(_)
224 )
225 );
226 if needs_parens {
227 format!("({elem_formatted})[]")
228 } else {
229 format!("{elem_formatted}[]")
230 }
231 }
232 TypeData::Tuple(elements) => {
233 let elements = self.interner.tuple_list(*elements);
234 self.format_tuple(elements.as_ref())
235 }
236 TypeData::Function(shape_id) => {
237 let shape = self.interner.function_shape(*shape_id);
238 self.format_function(shape.as_ref())
239 }
240 TypeData::Callable(shape_id) => {
241 let shape = self.interner.callable_shape(*shape_id);
242 self.format_callable(shape.as_ref())
243 }
244 TypeData::TypeParameter(info) => self.atom(info.name).to_string(),
245 TypeData::Lazy(def_id) => self.format_def_id(*def_id, "Lazy"),
246 TypeData::Recursive(idx) => {
247 format!("Recursive({idx})")
248 }
249 TypeData::BoundParameter(idx) => {
250 format!("BoundParameter({idx})")
251 }
252 TypeData::Application(app) => {
253 let app = self.interner.type_application(*app);
254 let base_key = self.interner.lookup(app.base);
255
256 trace!(
257 base_type_id = %app.base.0,
258 ?base_key,
259 args_count = app.args.len(),
260 "Formatting Application"
261 );
262
263 let base_str = if let Some(TypeData::Lazy(def_id)) = base_key {
266 let name = self.format_def_id(def_id, "Lazy");
267 trace!(
268 def_id = %def_id.0,
269 name = %name,
270 "Application base resolved from DefId"
271 );
272 name
273 } else {
274 let formatted = self.format(app.base);
275 trace!(
276 base_formatted = %formatted,
277 "Application base formatted (not Lazy)"
278 );
279 formatted
280 };
281
282 let args: Vec<String> = app.args.iter().map(|&arg| self.format(arg)).collect();
283 let result = format!("{}<{}>", base_str, args.join(", "));
284 trace!(result = %result, "Application formatted");
285 result
286 }
287 TypeData::Conditional(cond_id) => {
288 let cond = self.interner.conditional_type(*cond_id);
289 self.format_conditional(cond.as_ref())
290 }
291 TypeData::Mapped(mapped_id) => {
292 let mapped = self.interner.mapped_type(*mapped_id);
293 self.format_mapped(mapped.as_ref())
294 }
295 TypeData::IndexAccess(obj, idx) => {
296 format!("{}[{}]", self.format(*obj), self.format(*idx))
297 }
298 TypeData::TemplateLiteral(spans) => {
299 let spans = self.interner.template_list(*spans);
300 self.format_template_literal(spans.as_ref())
301 }
302 TypeData::TypeQuery(sym) => {
303 let name = if let Some(name) = self.format_symbol_name(SymbolId(sym.0)) {
304 name
305 } else {
306 format!("Ref({})", sym.0)
307 };
308 format!("typeof {name}")
309 }
310 TypeData::KeyOf(operand) => format!("keyof {}", self.format(*operand)),
311 TypeData::ReadonlyType(inner) => format!("readonly {}", self.format(*inner)),
312 TypeData::NoInfer(inner) => format!("NoInfer<{}>", self.format(*inner)),
313 TypeData::UniqueSymbol(sym) => {
314 let name = if let Some(name) = self.format_symbol_name(SymbolId(sym.0)) {
315 name
316 } else {
317 format!("symbol({})", sym.0)
318 };
319 format!("unique symbol {name}")
320 }
321 TypeData::Infer(info) => format!("infer {}", self.atom(info.name)),
322 TypeData::ThisType => "this".to_string(),
323 TypeData::StringIntrinsic { kind, type_arg } => {
324 let kind_name = match kind {
325 StringIntrinsicKind::Uppercase => "Uppercase",
326 StringIntrinsicKind::Lowercase => "Lowercase",
327 StringIntrinsicKind::Capitalize => "Capitalize",
328 StringIntrinsicKind::Uncapitalize => "Uncapitalize",
329 };
330 format!("{}<{}>", kind_name, self.format(*type_arg))
331 }
332 TypeData::Enum(def_id, _member_type) => self.format_def_id(*def_id, "Enum"),
333 TypeData::ModuleNamespace(sym) => {
334 let name = if let Some(name) = self.format_symbol_name(SymbolId(sym.0)) {
335 name
336 } else {
337 format!("module({})", sym.0)
338 };
339 format!("typeof import(\"{name}\")")
340 }
341 TypeData::Error => "error".to_string(),
342 }
343 }
344
345 fn format_intrinsic(&self, kind: IntrinsicKind) -> String {
346 match kind {
347 IntrinsicKind::Any => "any",
348 IntrinsicKind::Unknown => "unknown",
349 IntrinsicKind::Never => "never",
350 IntrinsicKind::Void => "void",
351 IntrinsicKind::Null => "null",
352 IntrinsicKind::Undefined => "undefined",
353 IntrinsicKind::Boolean => "boolean",
354 IntrinsicKind::Number => "number",
355 IntrinsicKind::String => "string",
356 IntrinsicKind::Bigint => "bigint",
357 IntrinsicKind::Symbol => "symbol",
358 IntrinsicKind::Object => "object",
359 IntrinsicKind::Function => "Function",
360 }
361 .to_string()
362 }
363
364 fn format_literal(&mut self, lit: &LiteralValue) -> String {
365 match lit {
366 LiteralValue::String(s) => format!("\"{}\"", self.atom(*s)),
367 LiteralValue::Number(n) => format!("{}", n.0),
368 LiteralValue::BigInt(b) => format!("{}n", self.atom(*b)),
369 LiteralValue::Boolean(b) => if *b { "true" } else { "false" }.to_string(),
370 }
371 }
372
373 fn format_object(&mut self, props: &[PropertyInfo]) -> String {
374 if props.is_empty() {
375 return "{}".to_string();
376 }
377 if props.len() > 3 {
378 let first_three: Vec<String> = props
379 .iter()
380 .take(3)
381 .map(|p| self.format_property(p))
382 .collect();
383 return format!("{{ {}; ...; }}", first_three.join("; "));
384 }
385 let formatted: Vec<String> = props.iter().map(|p| self.format_property(p)).collect();
386 format!("{{ {}; }}", formatted.join("; "))
387 }
388
389 fn format_property(&mut self, prop: &PropertyInfo) -> String {
390 let optional = if prop.optional { "?" } else { "" };
391 let readonly = if prop.readonly { "readonly " } else { "" };
392 let type_str = self.format(prop.type_id);
393 let name = self.atom(prop.name);
394 format!("{readonly}{name}{optional}: {type_str}")
395 }
396
397 fn format_type_params(&mut self, type_params: &[TypeParamInfo]) -> String {
398 if type_params.is_empty() {
399 return String::new();
400 }
401
402 let mut parts = Vec::with_capacity(type_params.len());
403 for tp in type_params {
404 let mut part = String::new();
405 if tp.is_const {
406 part.push_str("const ");
407 }
408 part.push_str(self.atom(tp.name).as_ref());
409 if let Some(constraint) = tp.constraint {
410 part.push_str(" extends ");
411 part.push_str(&self.format(constraint));
412 }
413 if let Some(default) = tp.default {
414 part.push_str(" = ");
415 part.push_str(&self.format(default));
416 }
417 parts.push(part);
418 }
419
420 format!("<{}>", parts.join(", "))
421 }
422
423 fn format_params(&mut self, params: &[ParamInfo], this_type: Option<TypeId>) -> Vec<String> {
424 let mut rendered = Vec::with_capacity(params.len() + usize::from(this_type.is_some()));
425
426 if let Some(this_ty) = this_type {
427 rendered.push(format!("this: {}", self.format(this_ty)));
428 }
429
430 for p in params {
431 let name = p
432 .name
433 .map_or_else(|| "_".to_string(), |atom| self.atom(atom).to_string());
434 let optional = if p.optional { "?" } else { "" };
435 let rest = if p.rest { "..." } else { "" };
436 let type_str = self.format(p.type_id);
437 rendered.push(format!("{rest}{name}{optional}: {type_str}"));
438 }
439
440 rendered
441 }
442
443 fn format_signature(
445 &mut self,
446 type_params: &[TypeParamInfo],
447 params: &[ParamInfo],
448 this_type: Option<TypeId>,
449 return_type: TypeId,
450 is_construct: bool,
451 separator: &str,
452 ) -> String {
453 let prefix = if is_construct { "new " } else { "" };
454 let type_params = self.format_type_params(type_params);
455 let params = self.format_params(params, this_type);
456 let return_str = if is_construct && return_type == TypeId::UNKNOWN {
457 "any".to_string()
458 } else {
459 self.format(return_type)
460 };
461 format!(
462 "{}{}({}) {} {}",
463 prefix,
464 type_params,
465 params.join(", "),
466 separator,
467 return_str
468 )
469 }
470
471 fn format_object_with_index(&mut self, shape: &ObjectShape) -> String {
472 let mut parts = Vec::new();
473
474 if let Some(ref idx) = shape.string_index {
475 parts.push(format!("[index: string]: {}", self.format(idx.value_type)));
476 }
477 if let Some(ref idx) = shape.number_index {
478 parts.push(format!("[index: number]: {}", self.format(idx.value_type)));
479 }
480 for prop in &shape.properties {
481 parts.push(self.format_property(prop));
482 }
483
484 format!("{{ {}; }}", parts.join("; "))
485 }
486
487 fn format_union(&mut self, members: &[TypeId]) -> String {
488 if members.len() > self.max_union_members {
489 let first: Vec<String> = members
490 .iter()
491 .take(self.max_union_members)
492 .map(|&m| self.format(m))
493 .collect();
494 return format!("{} | ...", first.join(" | "));
495 }
496 let formatted: Vec<String> = members.iter().map(|&m| self.format(m)).collect();
497 formatted.join(" | ")
498 }
499
500 fn format_intersection(&mut self, members: &[TypeId]) -> String {
501 let formatted: Vec<String> = members.iter().map(|&m| self.format(m)).collect();
502 formatted.join(" & ")
503 }
504
505 fn format_tuple(&mut self, elements: &[TupleElement]) -> String {
506 let formatted: Vec<String> = elements
507 .iter()
508 .map(|e| {
509 let rest = if e.rest { "..." } else { "" };
510 let optional = if e.optional { "?" } else { "" };
511 let type_str = self.format(e.type_id);
512 if let Some(name_atom) = e.name {
513 let name = self.atom(name_atom);
514 format!("{name}{optional}: {rest}{type_str}")
515 } else {
516 format!("{rest}{type_str}{optional}")
517 }
518 })
519 .collect();
520 format!("[{}]", formatted.join(", "))
521 }
522
523 fn format_function(&mut self, shape: &FunctionShape) -> String {
524 self.format_signature(
525 &shape.type_params,
526 &shape.params,
527 shape.this_type,
528 shape.return_type,
529 shape.is_constructor,
530 "=>",
531 )
532 }
533
534 fn format_callable(&mut self, shape: &CallableShape) -> String {
535 if !shape.construct_signatures.is_empty()
536 && let Some(sym_id) = shape.symbol
537 && let Some(name) = self.format_symbol_name(sym_id)
538 {
539 return format!("typeof {name}");
540 }
541
542 let has_index = shape.string_index.is_some() || shape.number_index.is_some();
543 if !has_index && shape.properties.is_empty() {
544 if shape.call_signatures.len() == 1 && shape.construct_signatures.is_empty() {
545 let sig = &shape.call_signatures[0];
546 return self.format_signature(
547 &sig.type_params,
548 &sig.params,
549 sig.this_type,
550 sig.return_type,
551 false,
552 "=>",
553 );
554 }
555 if shape.construct_signatures.len() == 1 && shape.call_signatures.is_empty() {
556 let sig = &shape.construct_signatures[0];
557 return self.format_signature(
558 &sig.type_params,
559 &sig.params,
560 sig.this_type,
561 sig.return_type,
562 true,
563 "=>",
564 );
565 }
566 }
567
568 let mut parts = Vec::new();
569 for sig in &shape.call_signatures {
570 parts.push(self.format_call_signature(sig, false));
571 }
572 for sig in &shape.construct_signatures {
573 parts.push(self.format_call_signature(sig, true));
574 }
575 if let Some(ref idx) = shape.string_index {
576 parts.push(format!("[index: string]: {}", self.format(idx.value_type)));
577 }
578 if let Some(ref idx) = shape.number_index {
579 parts.push(format!("[index: number]: {}", self.format(idx.value_type)));
580 }
581 let mut sorted_props: Vec<&PropertyInfo> = shape.properties.iter().collect();
582 sorted_props.sort_by(|a, b| {
583 self.interner
584 .resolve_atom_ref(a.name)
585 .cmp(&self.interner.resolve_atom_ref(b.name))
586 });
587 for prop in sorted_props {
588 parts.push(self.format_property(prop));
589 }
590
591 if parts.is_empty() {
592 return "{}".to_string();
593 }
594
595 format!("{{ {}; }}", parts.join("; "))
596 }
597
598 fn format_call_signature(&mut self, sig: &CallSignature, is_construct: bool) -> String {
599 self.format_signature(
600 &sig.type_params,
601 &sig.params,
602 sig.this_type,
603 sig.return_type,
604 is_construct,
605 ":",
606 )
607 }
608
609 fn format_conditional(&mut self, cond: &ConditionalType) -> String {
610 format!(
611 "{} extends {} ? {} : {}",
612 self.format(cond.check_type),
613 self.format(cond.extends_type),
614 self.format(cond.true_type),
615 self.format(cond.false_type)
616 )
617 }
618
619 fn format_mapped(&mut self, mapped: &MappedType) -> String {
620 format!(
621 "{{ [K in {}]: {} }}",
622 self.format(mapped.constraint),
623 self.format(mapped.template)
624 )
625 }
626
627 fn format_template_literal(&mut self, spans: &[TemplateSpan]) -> String {
628 let mut result = String::from("`");
629 for span in spans {
630 match span {
631 TemplateSpan::Text(text) => {
632 let text = self.atom(*text);
633 result.push_str(text.as_ref());
634 }
635 TemplateSpan::Type(type_id) => {
636 result.push_str("${");
637 result.push_str(&self.format(*type_id));
638 result.push('}');
639 }
640 }
641 }
642 result.push('`');
643 result
644 }
645
646 fn format_def_id(&mut self, def_id: crate::def::DefId, fallback_prefix: &str) -> String {
649 if let Some(def_store) = self.def_store
650 && let Some(def) = def_store.get(def_id)
651 {
652 return self.format_def_name(&def);
653 }
654 format!("{}({})", fallback_prefix, def_id.0)
655 }
656
657 fn resolve_object_shape_name(&mut self, shape: &ObjectShape) -> Option<String> {
659 if let Some(sym_id) = shape.symbol
660 && let Some(name) = self.format_symbol_name(sym_id)
661 {
662 return Some(name);
663 }
664 if let Some(def_store) = self.def_store
665 && let Some(def_id) = def_store.find_def_by_shape(shape)
666 && let Some(def) = def_store.get(def_id)
667 {
668 return Some(self.format_def_name(&def));
669 }
670 None
671 }
672
673 fn format_symbol_name(&mut self, sym_id: SymbolId) -> Option<String> {
674 let arena = self.symbol_arena?;
675 let sym = arena.get(sym_id)?;
676 let mut qualified_name = sym.escaped_name.to_string();
677 let mut current_parent = sym.parent;
678
679 while current_parent != SymbolId::NONE {
680 if let Some(parent_sym) = arena.get(current_parent) {
681 if !parent_sym.escaped_name.starts_with('"')
683 && !parent_sym.escaped_name.starts_with("__")
684 {
685 qualified_name = format!("{}.{}", parent_sym.escaped_name, qualified_name);
686 }
687 current_parent = parent_sym.parent;
688 } else {
689 break;
690 }
691 }
692
693 Some(qualified_name)
694 }
695
696 fn format_def_name(&mut self, def: &crate::def::DefinitionInfo) -> String {
697 if let Some(sym_id) = def.symbol_id
698 && let Some(qualified_name) = self.format_symbol_name(SymbolId(sym_id))
699 {
700 return qualified_name;
701 }
702
703 self.atom(def.name).to_string()
704 }
705}