Skip to main content

rib/type_inference/
type_hint.rs

1use crate::wit_type::WitType;
2use crate::{InferredType, TypeInternal};
3use std::fmt;
4use std::ops::Deref;
5
6// `TypeHint` is a simplified form of `InferredType`
7// It can capture partial type information (e.g., `List(None)` all the  way full type information such
8// as `List(Some(Number))`).
9// It supports early checks like `inferred_type.get_type_hint() == analysed_type.get_type_hint()`.
10//
11// As compilation progresses, `TypeHint` may get refined and can help with error reporting at various
12// stages even if the type information is not fully available.
13pub trait GetTypeHint {
14    fn get_type_hint(&self) -> TypeHint;
15}
16
17#[derive(PartialEq, Clone, Debug)]
18pub enum TypeHint {
19    Record(Option<Vec<(String, TypeHint)>>),
20    Tuple(Option<Vec<TypeHint>>),
21    Flag(Option<Vec<String>>),
22    Str,
23    Number,
24    List(Option<Box<TypeHint>>),
25    Boolean,
26    Option(Option<Box<TypeHint>>),
27    Enum(Option<Vec<String>>),
28    Char,
29    Result {
30        ok: Option<Box<TypeHint>>,
31        err: Option<Box<TypeHint>>,
32    },
33    Resource,
34    Variant(Option<Vec<(String, Option<TypeHint>)>>),
35    Unknown,
36    Ambiguous {
37        possibilities: Vec<TypeHint>,
38    },
39    Range,
40}
41
42impl TypeHint {
43    pub fn get_type_kind(&self) -> String {
44        match self {
45            TypeHint::Record(_) => "record".to_string(),
46            TypeHint::Tuple(_) => "tuple".to_string(),
47            TypeHint::Flag(_) => "flag".to_string(),
48            TypeHint::Str => "str".to_string(),
49            TypeHint::Number => "number".to_string(),
50            TypeHint::List(_) => "list".to_string(),
51            TypeHint::Boolean => "boolean".to_string(),
52            TypeHint::Option(_) => "option".to_string(),
53            TypeHint::Enum(_) => "enum".to_string(),
54            TypeHint::Char => "char".to_string(),
55            TypeHint::Result { .. } => "result".to_string(),
56            TypeHint::Resource => "resource".to_string(),
57            TypeHint::Variant(_) => "variant".to_string(),
58            TypeHint::Unknown => "unknown".to_string(),
59            TypeHint::Ambiguous { .. } => "ambiguous".to_string(),
60            TypeHint::Range => "range".to_string(),
61        }
62    }
63}
64
65impl fmt::Display for TypeHint {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        match self {
68            TypeHint::Record(Some(fields)) => {
69                write!(f, "record{{")?;
70                for (i, (name, kind)) in fields.iter().enumerate() {
71                    if i > 0 {
72                        write!(f, ", ")?;
73                    }
74                    write!(f, "{name}: {kind}")?;
75                }
76                write!(f, "}}")
77            }
78            TypeHint::Record(None) => write!(f, "record"),
79
80            TypeHint::Tuple(Some(types)) => {
81                write!(f, "tuple<")?;
82                for (i, kind) in types.iter().enumerate() {
83                    if i > 0 {
84                        write!(f, ", ")?;
85                    }
86                    write!(f, "{kind}")?;
87                }
88                write!(f, ">")
89            }
90            TypeHint::Tuple(None) => write!(f, "tuple"),
91
92            TypeHint::Flag(Some(flags)) => {
93                write!(f, "{{")?;
94                for (i, flag) in flags.iter().enumerate() {
95                    if i > 0 {
96                        write!(f, ", ")?;
97                    }
98                    write!(f, "{flag}")?;
99                }
100                write!(f, "}}")
101            }
102            TypeHint::Flag(None) => write!(f, "flag"),
103
104            TypeHint::Str => write!(f, "string"),
105            TypeHint::Number => write!(f, "number"),
106            TypeHint::List(None) => write!(f, "list"),
107            TypeHint::List(Some(typ)) => {
108                write!(f, "list<")?;
109                write!(f, "{typ}")?;
110                write!(f, ">")
111            }
112            TypeHint::Boolean => write!(f, "boolean"),
113            TypeHint::Option(None) => write!(f, "option"),
114            TypeHint::Option(Some(inner)) => {
115                write!(f, "option<")?;
116                write!(f, "{}", inner.deref())?;
117                write!(f, ">")
118            }
119            TypeHint::Enum(None) => write!(f, "enum"),
120            TypeHint::Enum(Some(enums)) => {
121                write!(f, "enum{{")?;
122                for (i, enum_name) in enums.iter().enumerate() {
123                    if i > 0 {
124                        write!(f, ", ")?;
125                    }
126                    write!(f, "{enum_name}")?;
127                }
128                write!(f, "}}")
129            }
130            TypeHint::Char => write!(f, "char"),
131            TypeHint::Result { ok, err } => {
132                write!(f, "result<")?;
133                if let Some(ok) = ok {
134                    write!(f, "{ok}")?;
135                } else {
136                    write!(f, "_")?;
137                }
138                write!(f, ", ")?;
139                if let Some(err) = err {
140                    write!(f, "{err}")?;
141                } else {
142                    write!(f, "_")?;
143                }
144                write!(f, ">")
145            }
146            TypeHint::Resource => write!(f, "resource"),
147            TypeHint::Variant(Some(variants)) => {
148                write!(f, "variant{{")?;
149                for (i, (name, kind)) in variants.iter().enumerate() {
150                    if i > 0 {
151                        write!(f, ", ")?;
152                    }
153                    write!(
154                        f,
155                        "{}: {}",
156                        name,
157                        kind.clone().map_or("_".to_string(), |x| x.to_string())
158                    )?;
159                }
160                write!(f, "}}")
161            }
162            TypeHint::Variant(None) => write!(f, "variant"),
163            TypeHint::Unknown => write!(f, "unknown"),
164            TypeHint::Range => write!(f, "range"),
165
166            TypeHint::Ambiguous { possibilities } => {
167                write!(f, "conflicting types: ")?;
168                for (i, kind) in possibilities.iter().enumerate() {
169                    if i > 0 {
170                        write!(f, ", ")?;
171                    }
172                    write!(f, "{kind}")?;
173                }
174                Ok(())
175            }
176        }
177    }
178}
179
180impl GetTypeHint for WitType {
181    fn get_type_hint(&self) -> TypeHint {
182        match self {
183            WitType::Record(fields) => {
184                let fields = fields
185                    .fields
186                    .iter()
187                    .map(|name_tpe| (name_tpe.name.clone(), name_tpe.typ.get_type_hint()))
188                    .collect();
189                TypeHint::Record(Some(fields))
190            }
191            WitType::Tuple(elems) => {
192                let elems = elems.items.iter().map(|tpe| tpe.get_type_hint()).collect();
193                TypeHint::Tuple(Some(elems))
194            }
195            WitType::Flags(flags) => {
196                let flags = flags.names.clone();
197                TypeHint::Flag(Some(flags))
198            }
199            WitType::Str(_) => TypeHint::Str,
200            WitType::S8(_) => TypeHint::Number,
201            WitType::U8(_) => TypeHint::Number,
202            WitType::S16(_) => TypeHint::Number,
203            WitType::U16(_) => TypeHint::Number,
204            WitType::S32(_) => TypeHint::Number,
205            WitType::U32(_) => TypeHint::Number,
206            WitType::S64(_) => TypeHint::Number,
207            WitType::U64(_) => TypeHint::Number,
208            WitType::F32(_) => TypeHint::Number,
209            WitType::F64(_) => TypeHint::Number,
210            WitType::Chr(_) => TypeHint::Char,
211            WitType::List(tpe) => {
212                let inner = tpe.inner.get_type_hint();
213                TypeHint::List(Some(Box::new(inner)))
214            }
215            WitType::Bool(_) => TypeHint::Boolean,
216            WitType::Option(tpe) => {
217                let inner = tpe.inner.get_type_hint();
218                TypeHint::Option(Some(Box::new(inner)))
219            }
220            WitType::Enum(tpe) => {
221                let variants = tpe.cases.clone();
222                TypeHint::Enum(Some(variants))
223            }
224            WitType::Result(tpe_result) => {
225                let ok: &Option<Box<WitType>> = &tpe_result.ok;
226                let err: &Option<Box<WitType>> = &tpe_result.err;
227                let ok = ok.as_ref().map(|tpe| tpe.get_type_hint());
228                let err = err.as_ref().map(|tpe| tpe.get_type_hint());
229                TypeHint::Result {
230                    ok: ok.map(Box::new),
231                    err: err.map(Box::new),
232                }
233            }
234            WitType::Handle(_) => TypeHint::Resource,
235            WitType::Variant(variants) => {
236                let variants = variants
237                    .cases
238                    .iter()
239                    .map(|name_tpe| {
240                        (
241                            name_tpe.name.clone(),
242                            name_tpe.typ.clone().map(|tpe| tpe.get_type_hint()),
243                        )
244                    })
245                    .collect();
246                TypeHint::Variant(Some(variants))
247            }
248        }
249    }
250}
251
252impl GetTypeHint for InferredType {
253    fn get_type_hint(&self) -> TypeHint {
254        match self.internal_type() {
255            TypeInternal::Bool => TypeHint::Boolean,
256            TypeInternal::S8
257            | TypeInternal::U8
258            | TypeInternal::S16
259            | TypeInternal::U16
260            | TypeInternal::S32
261            | TypeInternal::U32
262            | TypeInternal::S64
263            | TypeInternal::U64
264            | TypeInternal::F32
265            | TypeInternal::F64 => TypeHint::Number,
266            TypeInternal::Chr => TypeHint::Char,
267            TypeInternal::Str => TypeHint::Str,
268            TypeInternal::List(inferred_type) => {
269                TypeHint::List(Some(Box::new(inferred_type.get_type_hint())))
270            }
271            TypeInternal::Tuple(tuple) => {
272                TypeHint::Tuple(Some(tuple.iter().map(GetTypeHint::get_type_hint).collect()))
273            }
274            TypeInternal::Record(record) => TypeHint::Record(Some(
275                record
276                    .iter()
277                    .map(|(name, tpe)| (name.to_string(), tpe.get_type_hint()))
278                    .collect(),
279            )),
280            TypeInternal::Flags(flags) => {
281                TypeHint::Flag(Some(flags.iter().map(|x| x.to_string()).collect()))
282            }
283            TypeInternal::Enum(enums) => {
284                TypeHint::Enum(Some(enums.iter().map(|s| s.to_string()).collect()))
285            }
286            TypeInternal::Option(inner) => TypeHint::Option(Some(Box::new(inner.get_type_hint()))),
287            TypeInternal::Result { ok, error } => TypeHint::Result {
288                ok: ok.as_ref().map(|tpe| Box::new(tpe.get_type_hint())),
289                err: error.as_ref().map(|tpe| Box::new(tpe.get_type_hint())),
290            },
291            TypeInternal::Variant(variants) => TypeHint::Variant(Some(
292                variants
293                    .iter()
294                    .map(|(name, tpe)| {
295                        (
296                            name.to_string(),
297                            tpe.as_ref().map(GetTypeHint::get_type_hint),
298                        )
299                    })
300                    .collect(),
301            )),
302            TypeInternal::Resource { .. } => TypeHint::Resource,
303            TypeInternal::AllOf(possibilities) => get_type_kind(possibilities),
304            TypeInternal::Unknown | TypeInternal::Sequence(_) | TypeInternal::Instance { .. } => {
305                TypeHint::Unknown
306            }
307            TypeInternal::Range { .. } => TypeHint::Range,
308        }
309    }
310}
311
312fn get_type_kind(possibilities: &[InferredType]) -> TypeHint {
313    if let Some(first) = possibilities.first() {
314        let first = first.get_type_hint();
315        if possibilities.iter().all(|p| p.get_type_hint() == first) {
316            first
317        } else {
318            TypeHint::Ambiguous {
319                possibilities: possibilities.iter().map(|p| p.get_type_hint()).collect(),
320            }
321        }
322    } else {
323        TypeHint::Unknown
324    }
325}