Skip to main content

lisette_semantics/checker/registration/
convert.rs

1use rustc_hash::FxHashMap as HashMap;
2
3use syntax::EcoString;
4use syntax::ast::{Annotation, Generic, Span};
5use syntax::types::{SubstitutionMap, Type, substitute};
6
7use crate::checker::Checker;
8
9impl Checker<'_, '_> {
10    pub fn convert_to_type(&mut self, annotation: &Annotation, span: &Span) -> Type {
11        match annotation {
12            Annotation::Unknown => self.new_type_var(),
13
14            Annotation::Function {
15                params,
16                return_type,
17                ..
18            } => {
19                let new_params: Vec<Type> = params
20                    .iter()
21                    .map(|param| self.convert_to_type(param, span))
22                    .collect();
23                // For function type annotations, omitted return type means Unit (`()`),
24                // not a type variable. This ensures `fn(T)` is `fn(T) -> ()`.
25                let new_return_type = if matches!(return_type.as_ref(), Annotation::Unknown) {
26                    self.type_unit()
27                } else {
28                    self.convert_to_type(return_type, span)
29                };
30
31                Type::Function {
32                    param_mutability: vec![false; new_params.len()],
33                    params: new_params,
34                    bounds: Default::default(),
35                    return_type: new_return_type.into(),
36                }
37            }
38
39            Annotation::Constructor {
40                name: type_name,
41                params,
42                span: annotation_span,
43            } => {
44                // Unit is internal — `()` desugars to Constructor { name: "Unit" }.
45                // Return the interned unit type directly, unless a user-defined
46                // type named `Unit` exists in scope.
47                if type_name == "Unit"
48                    && params.is_empty()
49                    && self.resolve_type_name("Unit").is_none()
50                {
51                    return Type::unit();
52                }
53
54                if self.lookup_generic_index(type_name).is_some() {
55                    if !params.is_empty() {
56                        self.sink.push(diagnostics::infer::type_param_with_args(
57                            params.len(),
58                            *annotation_span,
59                        ));
60                    }
61                    return Type::Parameter(type_name.into());
62                }
63
64                let Some((qualified_name, ty)) =
65                    self.resolve_type_with_arity(type_name, params.len())
66                else {
67                    if type_name == "Self" {
68                        self.sink.push(diagnostics::infer::self_type_not_supported(
69                            *annotation_span,
70                        ));
71                    } else {
72                        self.sink.push(diagnostics::infer::type_not_found(
73                            type_name,
74                            *annotation_span,
75                        ));
76                    }
77                    return Type::Error;
78                };
79
80                self.track_name_usage(&qualified_name, annotation_span, type_name.len() as u32);
81
82                if qualified_name == "prelude.Unknown" && self.is_lis() {
83                    self.sink.push(diagnostics::infer::unknown_outside_typedef(
84                        *annotation_span,
85                    ));
86                }
87
88                let (generics, body) = match &ty {
89                    Type::Forall { vars, body } => (vars.clone(), body.as_ref().clone()),
90                    _ => (vec![], ty.clone()),
91                };
92
93                if generics.len() != params.len() {
94                    let actual_types: Vec<Type> = params
95                        .iter()
96                        .map(|arg| self.convert_to_type(arg, span))
97                        .collect();
98                    let generics_as_str: Vec<String> =
99                        generics.iter().map(|s| s.to_string()).collect();
100                    self.sink.push(diagnostics::infer::generics_arity_mismatch(
101                        &generics_as_str,
102                        params,
103                        &actual_types,
104                        *span,
105                    ));
106                }
107
108                let resolved_ty = self.instantiate_from_annotations(&generics, &body, params, span);
109
110                // Reject Ref<InterfaceType> — Go pointer-to-interface is invalid
111                if self.is_lis()
112                    && qualified_name == "prelude.Ref"
113                    && params.len() == 1
114                    && let Some(inner) = resolved_ty.inner()
115                    && let Some(inner_id) = inner.resolve().get_qualified_id()
116                    && self.store.get_interface(inner_id).is_some()
117                {
118                    self.sink.push(diagnostics::infer::ref_of_interface_type(
119                        &inner,
120                        *annotation_span,
121                    ));
122                }
123
124                if qualified_name == "prelude.Map"
125                    && !params.is_empty()
126                    && let Some(key_ty) = resolved_ty
127                        .get_type_params()
128                        .and_then(|p| p.first().cloned())
129                {
130                    self.check_map_key_comparable(&key_ty, *annotation_span);
131                }
132
133                resolved_ty
134            }
135
136            Annotation::Tuple { elements, .. } => {
137                let element_types = elements
138                    .iter()
139                    .map(|e| self.convert_to_type(e, span))
140                    .collect();
141                Type::Tuple(element_types)
142            }
143
144            Annotation::Opaque { .. } => {
145                unreachable!("Annotation::Opaque should not be converted to a type")
146            }
147        }
148    }
149
150    pub(super) fn resolve_type_with_arity(
151        &mut self,
152        type_name: &str,
153        expected_arity: usize,
154    ) -> Option<(String, Type)> {
155        let arity_of = |ty: &Type| match ty {
156            Type::Forall { vars, .. } => vars.len(),
157            _ => 0,
158        };
159
160        if let Some((qname, ty)) = self.resolve_type_name(type_name) {
161            if arity_of(&ty) == expected_arity {
162                return Some((qname, ty));
163            }
164            if !type_name.contains('.')
165                && let Some((pname, pty)) = self.resolve_type_from_prelude(type_name)
166                && arity_of(&pty) == expected_arity
167            {
168                return Some((pname, pty));
169            }
170            return Some((qname, ty));
171        }
172
173        self.resolve_type_from_prelude(type_name)
174    }
175
176    pub fn instantiate_from_annotations(
177        &mut self,
178        generics: &[EcoString],
179        body: &Type,
180        type_args: &[Annotation],
181        span: &Span,
182    ) -> Type {
183        let args: Vec<Type> = type_args
184            .iter()
185            .map(|arg_ann| self.convert_to_type(arg_ann, span))
186            .collect();
187
188        let map: SubstitutionMap = generics
189            .iter()
190            .zip(args.iter())
191            .map(|(name, ty)| (name.clone(), ty.clone()))
192            .collect();
193
194        substitute(body, &map)
195    }
196
197    /// Check that a map key type is comparable.
198    /// Only rejects concrete non-comparable types (Slice, Map, Function).
199    /// Type parameters are allowed here — they may be instantiated with comparable types.
200    /// Pre-check impl annotation for undeclared type params (e.g. `impl Container<T>`
201    /// without `impl<T>`). Adds them to scope to prevent cascading errors from
202    /// `convert_to_type`, and emits a diagnostic with the specific fix.
203    pub(crate) fn check_undeclared_impl_type_params(
204        &mut self,
205        annotation: &Annotation,
206        generics: &[Generic],
207    ) {
208        let Annotation::Constructor {
209            name: receiver_name,
210            params,
211            ..
212        } = annotation
213        else {
214            return;
215        };
216
217        let undeclared: Vec<_> = params
218            .iter()
219            .filter_map(|param| {
220                let Annotation::Constructor {
221                    name,
222                    params: sub_params,
223                    span: param_span,
224                } = param
225                else {
226                    return None;
227                };
228
229                // Single uppercase letter not declared as a type param — always a typo.
230                // Multi-letter names (Key, Error, etc.) are left to `type_not_found`.
231                if sub_params.is_empty()
232                    && name.len() == 1
233                    && name.chars().next().is_some_and(|c| c.is_uppercase())
234                    && self.lookup_generic_index(name).is_none()
235                {
236                    Some((name.to_string(), *param_span))
237                } else {
238                    None
239                }
240            })
241            .collect();
242
243        for (i, (name, param_span)) in undeclared.iter().enumerate() {
244            self.scopes
245                .current_mut()
246                .type_params
247                .get_or_insert_with(HashMap::default)
248                .insert(name.clone(), generics.len() + i);
249            self.sink
250                .push(diagnostics::infer::undeclared_impl_type_param(
251                    name,
252                    *param_span,
253                    receiver_name,
254                ));
255        }
256    }
257
258    fn check_map_key_comparable(&mut self, key_ty: &Type, span: Span) {
259        let resolved = key_ty.resolve();
260
261        let reason = match &resolved {
262            Type::Function { .. } => Some("functions"),
263            _ if resolved.has_name("Slice") => Some("slices"),
264            _ if resolved.has_name("Map") => Some("maps"),
265            _ => None,
266        };
267
268        if let Some(reason) = reason {
269            self.sink.push(diagnostics::infer::non_comparable_map_key(
270                &resolved, reason, span,
271            ));
272        }
273    }
274}