tinymist_analysis/ty/
describe.rs

1use 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    /// Describes the given type.
11    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    /// Describes available value instances of the given type.
20    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    /// Describe the given type.
30    pub fn describe(&self) -> Option<EcoString> {
31        let mut worker = TypeDescriber::default();
32        worker.describe_root(self)
33    }
34
35    // todo: extend this cache idea for all crate?
36    // #[allow(clippy::mutable_key_type)]
37    // let mut describe_cache = HashMap::<Ty, String>::new();
38    // let doc_ty = |ty: Option<&Ty>| {
39    //     let ty = ty?;
40    //     let short = {
41    //         describe_cache
42    //             .entry(ty.clone())
43    //             .or_insert_with(|| ty.describe().unwrap_or_else(||
44    // "unknown".to_string()))             .clone()
45    //     };
46
47    //     Some((short, format!("{ty:?}")))
48    // };
49}
50
51/// A worker to describe types.
52#[derive(Default)]
53struct TypeDescriber {
54    /// Whether to describe the representation of the type.
55    repr: bool,
56    /// Whether to describe the value instances of the type.
57    value: bool,
58    /// The cache to describe types.
59    described: HashMap<u128, EcoString>,
60    /// The results of the description.
61    results: HashSet<EcoString>,
62    /// The functions to describe.
63    functions: Vec<Interned<SigTy>>,
64}
65
66impl TypeDescriber {
67    /// Describes the given type.
68    fn describe_root(&mut self, ty: &Ty) -> Option<EcoString> {
69        let _ = TypeDescriber::describe_iter;
70        // recursive structure
71        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            // todo: union signature
87            // only first function is described
88            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    /// Describes the given types.
143    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    /// Describes the given type.
153    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            // todo: sig with
176            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            // Doesn't provide any information, hence we doesn't describe it intermediately here.
186            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}