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