tinymist_analysis/ty/
describe.rs1use ecow::{EcoString, eco_format};
2use tinymist_std::hash::hash128;
3use tinymist_world::vfs::WorkspaceResolver;
4use typst::foundations::Repr;
5
6use super::{is_plain_value, term_value};
7use crate::{ty::prelude::*, upstream::truncated_repr_};
8
9impl Ty {
10 pub fn repr(&self) -> Option<EcoString> {
12 let mut worker = TypeDescriber {
13 repr: true,
14 ..Default::default()
15 };
16 worker.describe_root(self)
17 }
18
19 pub fn value_repr(&self) -> Option<EcoString> {
21 let mut worker = TypeDescriber {
22 repr: true,
23 value: true,
24 ..Default::default()
25 };
26 worker.describe_root(self)
27 }
28
29 pub fn describe(&self) -> Option<EcoString> {
31 let mut worker = TypeDescriber::default();
32 worker.describe_root(self)
33 }
34
35 }
50
51#[derive(Default)]
53struct TypeDescriber {
54 repr: bool,
56 value: bool,
58 described: HashMap<u128, EcoString>,
60 results: HashSet<EcoString>,
62 functions: Vec<Interned<SigTy>>,
64}
65
66impl TypeDescriber {
67 fn describe_root(&mut self, ty: &Ty) -> Option<EcoString> {
69 let _ = TypeDescriber::describe_iter;
70 if let Some(t) = self.described.get(&hash128(ty)) {
72 return Some(t.clone());
73 }
74
75 let res = self.describe(ty);
76 if !res.is_empty() {
77 return Some(res);
78 }
79 self.described.insert(hash128(ty), "$self".into());
80
81 let mut results = std::mem::take(&mut self.results)
82 .into_iter()
83 .collect::<Vec<_>>();
84 let functions = std::mem::take(&mut self.functions);
85 if !functions.is_empty() {
86 let func = functions[0].clone();
89
90 let mut res = EcoString::new();
91 res.push('(');
92 let mut not_first = false;
93 for ty in func.positional_params() {
94 if not_first {
95 res.push_str(", ");
96 } else {
97 not_first = true;
98 }
99 res.push_str(self.describe_root(ty).as_deref().unwrap_or("any"));
100 }
101 for (name, ty) in func.named_params() {
102 if not_first {
103 res.push_str(", ");
104 } else {
105 not_first = true;
106 }
107 res.push_str(name);
108 res.push_str(": ");
109 res.push_str(self.describe_root(ty).as_deref().unwrap_or("any"));
110 }
111 if let Some(spread_right) = func.rest_param() {
112 if not_first {
113 res.push_str(", ");
114 }
115 res.push_str("..: ");
116 res.push_str(self.describe_root(spread_right).as_deref().unwrap_or("any"));
117 }
118 res.push_str(") => ");
119 res.push_str(
120 func.body
121 .as_ref()
122 .and_then(|ret| self.describe_root(ret))
123 .as_deref()
124 .unwrap_or("any"),
125 );
126
127 results.push(res);
128 }
129
130 if results.is_empty() {
131 self.described.insert(hash128(ty), "any".into());
132 return None;
133 }
134
135 results.sort();
136 results.dedup();
137 let res: EcoString = results.join(" | ").into();
138 self.described.insert(hash128(ty), res.clone());
139 Some(res)
140 }
141
142 fn describe_iter(&mut self, ty: &[Ty]) {
144 for ty in ty.iter() {
145 let desc = self.describe(ty);
146 if !desc.is_empty() {
147 self.results.insert(desc);
148 }
149 }
150 }
151
152 fn describe(&mut self, ty: &Ty) -> EcoString {
154 match ty {
155 Ty::Var(..) => {}
156 Ty::Union(types) => {
157 self.describe_iter(types);
158 }
159 Ty::Let(bounds) => {
160 self.describe_iter(&bounds.lbs);
161 self.describe_iter(&bounds.ubs);
162 }
163 Ty::Func(func) => {
164 self.functions.push(func.clone());
165 }
166 Ty::Dict(..) => {
167 return "dictionary".into();
168 }
169 Ty::Tuple(..) => {
170 return "array".into();
171 }
172 Ty::Array(..) => {
173 return "array".into();
174 }
175 Ty::With(w) => {
177 return self.describe(&w.sig);
178 }
179 Ty::Builtin(BuiltinTy::Content(Some(elem))) => {
180 return elem.name().into();
181 }
182 Ty::Builtin(BuiltinTy::Content(None) | BuiltinTy::Space) => {
183 return "content".into();
184 }
185 Ty::Any | Ty::Builtin(BuiltinTy::Clause | BuiltinTy::Undef | BuiltinTy::Infer) => {}
187 Ty::Builtin(BuiltinTy::FlowNone | BuiltinTy::None) => {
188 return "none".into();
189 }
190 Ty::Builtin(BuiltinTy::Auto) => {
191 return "auto".into();
192 }
193 Ty::Boolean(..) if self.repr => {
194 return "bool".into();
195 }
196 Ty::Boolean(None) => {
197 return "bool".into();
198 }
199 Ty::Boolean(Some(b)) => {
200 return eco_format!("{b}");
201 }
202 Ty::Builtin(b) => {
203 return b.describe();
204 }
205 Ty::Value(v) if matches!(v.val, Value::Module(..)) => {
206 let Value::Module(m) = &v.val else {
207 return "module".into();
208 };
209
210 return match (m.name(), m.file_id()) {
211 (Some(name), ..) => eco_format!("module({name:?})"),
212 (None, file_id) => {
213 eco_format!("module({:?})", WorkspaceResolver::display(file_id))
214 }
215 };
216 }
217 Ty::Value(v) if !is_plain_value(&v.val) => return self.describe(&term_value(&v.val)),
218 Ty::Value(v) if self.value => return truncated_repr_::<181>(&v.val),
219 Ty::Value(v) if self.repr => return v.val.ty().short_name().into(),
220 Ty::Value(v) => return v.val.repr(),
221 Ty::Param(..) => {
222 return "param".into();
223 }
224 Ty::Args(..) => {
225 return "arguments".into();
226 }
227 Ty::Pattern(..) => {
228 return "pattern".into();
229 }
230 Ty::Select(..) | Ty::Unary(..) | Ty::Binary(..) | Ty::If(..) => return "any".into(),
231 }
232
233 EcoString::new()
234 }
235}