rib/type_inference/
type_hint.rs

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