Skip to main content

lisette_semantics/checker/registration/
convert.rs

1use rustc_hash::FxHashMap as HashMap;
2
3use crate::checker::EnvResolve;
4use syntax::EcoString;
5use syntax::ast::{Annotation, Generic, Span};
6use syntax::program::{Definition, DefinitionBody};
7use syntax::types::{SubstitutionMap, Type, substitute};
8
9use crate::checker::TaskState;
10use crate::store::Store;
11
12impl TaskState<'_> {
13    /// Resolves a generic-bound annotation. Bound-only markers like
14    /// `Comparable` are admitted here; the same names in value position
15    /// are flagged inside `convert_to_type`.
16    pub fn convert_bound_to_type(
17        &mut self,
18        store: &Store,
19        annotation: &Annotation,
20        span: &Span,
21    ) -> Type {
22        self.bound_position_depth += 1;
23        let result = self.convert_to_type(store, annotation, span);
24        self.bound_position_depth -= 1;
25        result
26    }
27
28    pub fn convert_to_type(&mut self, store: &Store, annotation: &Annotation, span: &Span) -> Type {
29        match annotation {
30            Annotation::Unknown => self.new_type_var(),
31
32            Annotation::Function {
33                params,
34                return_type,
35                ..
36            } => {
37                let new_params: Vec<Type> = params
38                    .iter()
39                    .map(|param| self.convert_to_type(store, param, span))
40                    .collect();
41                // For function type annotations, omitted return type means Unit (`()`),
42                // not a type variable. This ensures `fn(T)` is `fn(T) -> ()`.
43                let new_return_type = if matches!(return_type.as_ref(), Annotation::Unknown) {
44                    self.type_unit()
45                } else {
46                    self.convert_to_type(store, return_type, span)
47                };
48
49                Type::Function {
50                    param_mutability: vec![false; new_params.len()],
51                    params: new_params,
52                    bounds: Default::default(),
53                    return_type: new_return_type.into(),
54                }
55            }
56
57            Annotation::Constructor {
58                name: type_name,
59                params,
60                span: annotation_span,
61            } => {
62                // Unit is internal — `()` desugars to Constructor { name: "Unit" }.
63                // Return the interned unit type directly, unless a user-defined
64                // type named `Unit` exists in scope.
65                if type_name == "Unit"
66                    && params.is_empty()
67                    && self.resolve_type_name(store, "Unit").is_none()
68                {
69                    return Type::unit();
70                }
71
72                if self.lookup_generic_index(type_name).is_some() {
73                    if !params.is_empty() {
74                        self.sink.push(diagnostics::infer::type_param_with_args(
75                            params.len(),
76                            *annotation_span,
77                        ));
78                    }
79                    return Type::Parameter(type_name.into());
80                }
81
82                let Some((qualified_name, ty)) =
83                    self.resolve_type_with_arity(store, type_name, params.len())
84                else {
85                    if type_name == "Self" {
86                        let receiver = self.scopes.impl_receiver_type().map(|ty| ty.stringify());
87                        self.sink.push(diagnostics::infer::self_type_not_supported(
88                            *annotation_span,
89                            receiver.as_deref(),
90                        ));
91                    } else {
92                        self.sink.push(diagnostics::infer::type_not_found(
93                            type_name,
94                            *annotation_span,
95                        ));
96                    }
97                    return Type::Error;
98                };
99
100                self.track_name_usage(
101                    store,
102                    &qualified_name,
103                    annotation_span,
104                    type_name.len() as u32,
105                );
106
107                if self.bound_position_depth == 0
108                    && let Some(builtin) =
109                        crate::checker::infer::BuiltinBound::from_qualified_id(&qualified_name)
110                {
111                    self.sink
112                        .push(diagnostics::infer::bound_only_in_value_position(
113                            builtin.label(),
114                            *annotation_span,
115                        ));
116                    return Type::Error;
117                }
118
119                let (generics, body) = match &ty {
120                    Type::Forall { vars, body } => (vars.clone(), body.as_ref().clone()),
121                    _ => (vec![], ty.clone()),
122                };
123
124                if generics.len() != params.len() {
125                    let actual_types: Vec<Type> = params
126                        .iter()
127                        .map(|arg| self.convert_to_type(store, arg, span))
128                        .collect();
129                    let generics_as_str: Vec<String> =
130                        generics.iter().map(|s| s.to_string()).collect();
131                    self.sink.push(diagnostics::infer::generics_arity_mismatch(
132                        &generics_as_str,
133                        params,
134                        &actual_types,
135                        *span,
136                    ));
137                }
138
139                let concrete_args: Vec<Type> = params
140                    .iter()
141                    .map(|arg| self.convert_to_type(store, arg, span))
142                    .collect();
143                let map: SubstitutionMap = generics
144                    .iter()
145                    .cloned()
146                    .zip(concrete_args.iter().cloned())
147                    .collect();
148                let resolved_ty = substitute(&body, &map);
149
150                // Reject Ref<InterfaceType> — Go pointer-to-interface is invalid
151                if self.is_lis(store)
152                    && qualified_name == "prelude.Ref"
153                    && params.len() == 1
154                    && let Some(inner) = resolved_ty.inner()
155                {
156                    let peeled_inner = store.peel_alias(&inner.resolve_in(&self.env));
157                    if let Some(inner_id) = peeled_inner.get_qualified_id()
158                        && store.get_interface(inner_id).is_some()
159                    {
160                        self.sink.push(diagnostics::infer::ref_of_interface_type(
161                            &inner,
162                            *annotation_span,
163                        ));
164                    }
165                }
166
167                if qualified_name == "prelude.Map"
168                    && !params.is_empty()
169                    && let Some(key_ty) = resolved_ty
170                        .get_type_params()
171                        .and_then(|p| p.first().cloned())
172                {
173                    self.check_map_key_comparable(store, &key_ty, *annotation_span);
174                }
175
176                // Preserve alias name in emitter output. Guard against re-wrapping bodies whose
177                // id already matches (function aliases are pre-wrapped by populate_type_alias).
178                let body_differs = match &resolved_ty {
179                    Type::Nominal { id, .. } => id.as_str() != qualified_name.as_str(),
180                    Type::Simple(_) | Type::Compound { .. } => true,
181                    _ => false,
182                };
183                if body_differs
184                    && let Some(Definition {
185                        body:
186                            DefinitionBody::TypeAlias {
187                                annotation: alias_ann,
188                                ..
189                            },
190                        ..
191                    }) = store.get_definition(&qualified_name)
192                    && !alias_ann.is_opaque()
193                {
194                    return Type::Nominal {
195                        id: qualified_name.into(),
196                        params: concrete_args,
197                        underlying_ty: Some(Box::new(resolved_ty)),
198                    };
199                }
200
201                resolved_ty
202            }
203
204            Annotation::Tuple { elements, .. } => {
205                let element_types = elements
206                    .iter()
207                    .map(|e| self.convert_to_type(store, e, span))
208                    .collect();
209                Type::Tuple(element_types)
210            }
211
212            Annotation::Opaque { .. } => {
213                unreachable!("Annotation::Opaque should not be converted to a type")
214            }
215        }
216    }
217
218    pub(super) fn resolve_type_with_arity(
219        &mut self,
220        store: &Store,
221        type_name: &str,
222        expected_arity: usize,
223    ) -> Option<(String, Type)> {
224        let arity_of = |ty: &Type| match ty {
225            Type::Forall { vars, .. } => vars.len(),
226            _ => 0,
227        };
228
229        if let Some((qname, ty)) = self.resolve_type_name(store, type_name) {
230            if arity_of(&ty) == expected_arity {
231                return Some((qname, ty));
232            }
233            if !type_name.contains('.')
234                && let Some((pname, pty)) = self.resolve_type_from_prelude(store, type_name)
235                && arity_of(&pty) == expected_arity
236            {
237                return Some((pname, pty));
238            }
239            return Some((qname, ty));
240        }
241
242        self.resolve_type_from_prelude(store, type_name)
243    }
244
245    pub fn instantiate_from_annotations(
246        &mut self,
247        store: &Store,
248        generics: &[EcoString],
249        body: &Type,
250        type_args: &[Annotation],
251        span: &Span,
252    ) -> Type {
253        let args: Vec<Type> = type_args
254            .iter()
255            .map(|arg_ann| self.convert_to_type(store, arg_ann, span))
256            .collect();
257
258        let map: SubstitutionMap = generics
259            .iter()
260            .zip(args.iter())
261            .map(|(name, ty)| (name.clone(), ty.clone()))
262            .collect();
263
264        substitute(body, &map)
265    }
266
267    /// Check that a map key type is comparable.
268    /// Only rejects concrete non-comparable types (Slice, Map, Function).
269    /// Type parameters are allowed here — they may be instantiated with comparable types.
270    /// Pre-check impl annotation for undeclared type params (e.g. `impl Container<T>`
271    /// without `impl<T>`). Adds them to scope to prevent cascading errors from
272    /// `convert_to_type`, and emits a diagnostic with the specific fix.
273    pub(crate) fn check_undeclared_impl_type_params(
274        &mut self,
275        annotation: &Annotation,
276        generics: &[Generic],
277    ) {
278        let Annotation::Constructor {
279            name: receiver_name,
280            params,
281            ..
282        } = annotation
283        else {
284            return;
285        };
286
287        let undeclared: Vec<_> = params
288            .iter()
289            .filter_map(|param| {
290                let Annotation::Constructor {
291                    name,
292                    params: sub_params,
293                    span: param_span,
294                } = param
295                else {
296                    return None;
297                };
298
299                // Single uppercase letter not declared as a type param — always a typo.
300                // Multi-letter names (Key, Error, etc.) are left to `type_not_found`.
301                if sub_params.is_empty()
302                    && name.len() == 1
303                    && name.chars().next().is_some_and(|c| c.is_uppercase())
304                    && self.lookup_generic_index(name).is_none()
305                {
306                    Some((name.to_string(), *param_span))
307                } else {
308                    None
309                }
310            })
311            .collect();
312
313        for (i, (name, param_span)) in undeclared.iter().enumerate() {
314            self.scopes
315                .current_mut()
316                .type_params
317                .get_or_insert_with(HashMap::default)
318                .insert(name.clone(), generics.len() + i);
319            self.sink
320                .push(diagnostics::infer::undeclared_impl_type_param(
321                    name,
322                    *param_span,
323                    receiver_name,
324                ));
325        }
326    }
327
328    fn check_map_key_comparable(&mut self, store: &Store, key_ty: &Type, span: Span) {
329        let resolved = key_ty.resolve_in(&self.env);
330
331        if self.is_lis(store) && resolved.resolves_to_unknown() {
332            self.sink.push(diagnostics::infer::unknown_as_map_key(span));
333            return;
334        }
335
336        let reason = if matches!(&resolved, Type::Function { .. }) {
337            "functions"
338        } else if resolved.has_name("Slice") {
339            "slices"
340        } else if resolved.has_name("Map") {
341            "maps"
342        } else {
343            return;
344        };
345
346        self.sink.push(diagnostics::infer::non_comparable_map_key(
347            &resolved, reason, span,
348        ));
349    }
350}