1use id_arena::Arena;
2use indexmap::IndexMap;
3
4use crate::{Type, TypeId, substitute};
5
6pub fn stringify(arena: &mut Arena<Type>, id: TypeId) -> String {
7 let id = substitute(arena, id);
8 stringify_impl(arena, id, &mut IndexMap::new())
9}
10
11pub fn stringify_without_substitution(arena: &Arena<Type>, id: TypeId) -> String {
12 stringify_impl(arena, id, &mut IndexMap::new())
13}
14
15pub(crate) fn stringify_impl(
16 arena: &Arena<Type>,
17 id: TypeId,
18 stack: &mut IndexMap<TypeId, bool>,
19) -> String {
20 let len = stack.len();
21
22 if stack.contains_key(&id) {
23 stack.insert(id, true);
24 return format!("<{}>", stack.get_index_of(&id).unwrap());
25 }
26
27 stack.insert(id, false);
28
29 let result = match arena[id].clone() {
30 Type::Ref(id) => stringify_impl(arena, id, stack),
31 Type::Unresolved => "{unresolved}".to_string(),
32 Type::Generic(generic) => {
33 if let Some(name) = generic.name {
34 name.text().to_string()
35 } else {
36 format!("{{generic {}}}", id.index())
37 }
38 }
39 Type::Never => "Never".to_string(),
40 Type::Any => "Any".to_string(),
41 Type::Atom(atom) => atom.to_string(),
42 Type::Pair(pair) => {
43 let first = stringify_impl(arena, pair.first, stack);
44 let rest = stringify_impl(arena, pair.rest, stack);
45 format!("({first}, {rest})")
46 }
47 Type::Struct(ty) => {
48 if let Some(name) = ty.name {
49 name.text().to_string()
50 } else {
51 stringify_impl(arena, ty.inner, stack)
52 }
53 }
54 Type::Alias(alias) => {
55 if let Some(name) = alias.name {
56 name.text().to_string()
57 } else {
58 stringify_impl(arena, alias.inner, stack)
59 }
60 }
61 Type::Apply(apply) => {
62 let inner = stringify_impl(arena, apply.inner, stack);
63 let generics = apply
64 .generics
65 .values()
66 .map(|v| stringify_impl(arena, *v, stack))
67 .collect::<Vec<_>>()
68 .join(", ");
69 format!("{inner}<{generics}>")
70 }
71 Type::Function(function) => {
72 let params = function
73 .params
74 .iter()
75 .map(|(name, id)| format!("{}: {}", name, stringify_impl(arena, *id, stack)))
76 .collect::<Vec<_>>()
77 .join(", ");
78 let ret = stringify_impl(arena, function.ret, stack);
79 format!("fn({params}) -> {ret}")
80 }
81 Type::Union(union) => union
82 .types
83 .iter()
84 .map(|id| stringify_impl(arena, *id, stack))
85 .collect::<Vec<_>>()
86 .join(" | "),
87 };
88
89 let recursed = stack.pop().unwrap().1;
90
91 if recursed {
92 if result.starts_with('(') {
93 format!("{len} @ {result}")
94 } else {
95 format!("{len} @ ({result})")
96 }
97 } else {
98 result
99 }
100}