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, unqualified_name};
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                let param_mutability = vec![false; new_params.len()];
50                Type::function(
51                    new_params,
52                    param_mutability,
53                    Default::default(),
54                    new_return_type.into(),
55                )
56            }
57
58            Annotation::Constructor {
59                name: type_name,
60                params,
61                span: annotation_span,
62            } => {
63                // Unit is internal — `()` desugars to Constructor { name: "Unit" }.
64                // Return the interned unit type directly, unless a user-defined
65                // type named `Unit` exists in scope.
66                if type_name == "Unit"
67                    && params.is_empty()
68                    && self.resolve_type_name(store, "Unit").is_none()
69                {
70                    return Type::unit();
71                }
72
73                if self.lookup_generic_index(type_name).is_some() {
74                    if !params.is_empty() {
75                        self.sink.push(diagnostics::infer::type_param_with_args(
76                            params.len(),
77                            *annotation_span,
78                        ));
79                    }
80                    return Type::Parameter(type_name.into());
81                }
82
83                let Some((qualified_name, ty)) =
84                    self.resolve_type_with_arity(store, type_name, params.len())
85                else {
86                    if type_name == "Self" {
87                        let receiver = self.scopes.impl_receiver_type().map(|ty| ty.stringify());
88                        self.sink.push(diagnostics::infer::self_type_not_supported(
89                            *annotation_span,
90                            receiver.as_deref(),
91                        ));
92                    } else {
93                        self.sink.push(diagnostics::infer::type_not_found(
94                            type_name,
95                            *annotation_span,
96                        ));
97                    }
98                    return Type::Error;
99                };
100
101                if let Some((kind, help)) =
102                    self.classify_non_type_name(store, &qualified_name, type_name)
103                {
104                    self.sink.push(diagnostics::infer::value_in_type_position(
105                        type_name,
106                        kind,
107                        *annotation_span,
108                        help,
109                    ));
110                    return Type::Error;
111                }
112
113                self.track_name_usage(
114                    store,
115                    &qualified_name,
116                    annotation_span,
117                    type_name.len() as u32,
118                );
119
120                if self.bound_position_depth == 0
121                    && let Some(builtin) =
122                        crate::checker::infer::BuiltinBound::from_qualified_id(&qualified_name)
123                {
124                    self.sink
125                        .push(diagnostics::infer::bound_only_in_value_position(
126                            builtin.label(),
127                            *annotation_span,
128                        ));
129                    return Type::Error;
130                }
131
132                let (generics, body) = match ty {
133                    Type::Forall { vars, body } => (vars, *body),
134                    other => (vec![], other),
135                };
136
137                if generics.len() != params.len() {
138                    let actual_types: Vec<Type> = params
139                        .iter()
140                        .map(|arg| self.convert_to_type(store, arg, span))
141                        .collect();
142                    let generics_as_str: Vec<String> =
143                        generics.iter().map(|s| s.to_string()).collect();
144                    self.sink.push(diagnostics::infer::generics_arity_mismatch(
145                        &generics_as_str,
146                        params,
147                        &actual_types,
148                        *span,
149                    ));
150                }
151
152                let concrete_args: Vec<Type> = params
153                    .iter()
154                    .map(|arg| self.convert_to_type(store, arg, span))
155                    .collect();
156                let resolved_ty = if generics.is_empty() && concrete_args.is_empty() {
157                    body
158                } else {
159                    let map: SubstitutionMap = generics
160                        .iter()
161                        .cloned()
162                        .zip(concrete_args.iter().cloned())
163                        .collect();
164                    substitute(&body, &map)
165                };
166
167                // Reject Ref<InterfaceType> — Go pointer-to-interface is invalid
168                if self.is_lis(store)
169                    && qualified_name == "prelude.Ref"
170                    && params.len() == 1
171                    && let Some(inner) = resolved_ty.inner()
172                {
173                    let peeled_inner = store.peel_alias(&inner.resolve_in(&self.env));
174                    if let Some(inner_id) = peeled_inner.get_qualified_id()
175                        && store.get_interface(inner_id).is_some()
176                    {
177                        self.sink.push(diagnostics::infer::ref_of_interface_type(
178                            &inner,
179                            *annotation_span,
180                        ));
181                    }
182                }
183
184                if qualified_name == "prelude.Map"
185                    && !params.is_empty()
186                    && let Some(key_ty) = resolved_ty
187                        .get_type_params()
188                        .and_then(|p| p.first().cloned())
189                {
190                    self.check_map_key_comparable(store, &key_ty, *annotation_span);
191                }
192
193                // Preserve alias name in emitter output. Guard against re-wrapping bodies whose
194                // id already matches (function aliases are pre-wrapped by populate_type_alias).
195                let body_differs = match &resolved_ty {
196                    Type::Nominal { id, .. } => id.as_str() != qualified_name.as_str(),
197                    Type::Simple(_) | Type::Compound { .. } => true,
198                    _ => false,
199                };
200                if body_differs
201                    && let Some(Definition {
202                        body:
203                            DefinitionBody::TypeAlias {
204                                annotation: alias_ann,
205                                ..
206                            },
207                        ..
208                    }) = store.get_definition(&qualified_name)
209                    && !alias_ann.is_opaque()
210                {
211                    return Type::Nominal {
212                        id: qualified_name.into(),
213                        params: concrete_args,
214                        underlying_ty: Some(Box::new(resolved_ty)),
215                    };
216                }
217
218                resolved_ty
219            }
220
221            Annotation::Tuple { elements, .. } => {
222                let element_types = elements
223                    .iter()
224                    .map(|e| self.convert_to_type(store, e, span))
225                    .collect();
226                Type::Tuple(element_types)
227            }
228
229            Annotation::Opaque { .. } => {
230                unreachable!("Annotation::Opaque should not be converted to a type")
231            }
232        }
233    }
234
235    pub(super) fn classify_non_type_name(
236        &self,
237        store: &Store,
238        qualified_name: &str,
239        type_name: &str,
240    ) -> Option<(&'static str, Option<String>)> {
241        let definition = store.get_definition(qualified_name)?;
242        if !definition.is_value(qualified_name) {
243            return None;
244        }
245        let body = definition.ty.unwrap_forall();
246
247        let is_function = matches!(body, Type::Function(_));
248        let enum_id = match body {
249            Type::Function(f) => f.return_type.get_qualified_id(),
250            other => other.get_qualified_id(),
251        };
252        let variant_name = unqualified_name(qualified_name);
253        let parent_enum = enum_id.filter(|id| {
254            store.get_definition(id).is_some_and(|d| match &d.body {
255                DefinitionBody::Enum { variants, .. } => {
256                    variants.iter().any(|v| v.name == variant_name)
257                }
258                _ => false,
259            })
260        });
261
262        if let Some(enum_id) = parent_enum {
263            let enum_name = unqualified_name(enum_id);
264            let help = if is_function {
265                format!(
266                    "Use `{}` for the enum type, or call `{}(...)` to construct a value",
267                    enum_name, type_name
268                )
269            } else {
270                format!("Use `{}` for the enum type", enum_name)
271            };
272            return Some(("enum variant", Some(help)));
273        }
274
275        if is_function {
276            return Some((
277                "function",
278                Some("Use a function type alias or write the function type directly".to_string()),
279            ));
280        }
281
282        Some(("value", Some("Only a type is allowed here".to_string())))
283    }
284
285    pub(super) fn resolve_type_with_arity(
286        &mut self,
287        store: &Store,
288        type_name: &str,
289        expected_arity: usize,
290    ) -> Option<(String, Type)> {
291        let arity_of = |ty: &Type| match ty {
292            Type::Forall { vars, .. } => vars.len(),
293            _ => 0,
294        };
295
296        if !type_name.contains('.')
297            && is_reserved_prelude_generic(type_name)
298            && let Some((pname, pty)) = self.resolve_type_from_prelude(store, type_name)
299            && arity_of(&pty) == expected_arity
300        {
301            return Some((pname, pty));
302        }
303
304        if let Some((qname, ty)) = self.resolve_type_name(store, type_name) {
305            if arity_of(&ty) == expected_arity {
306                return Some((qname, ty));
307            }
308            if !type_name.contains('.')
309                && let Some((pname, pty)) = self.resolve_type_from_prelude(store, type_name)
310                && arity_of(&pty) == expected_arity
311            {
312                return Some((pname, pty));
313            }
314            return Some((qname, ty));
315        }
316
317        self.resolve_type_from_prelude(store, type_name)
318    }
319
320    pub fn instantiate_from_annotations(
321        &mut self,
322        store: &Store,
323        generics: &[EcoString],
324        body: &Type,
325        type_args: &[Annotation],
326        span: &Span,
327    ) -> Type {
328        let args: Vec<Type> = type_args
329            .iter()
330            .map(|arg_ann| self.convert_to_type(store, arg_ann, span))
331            .collect();
332
333        let map: SubstitutionMap = generics
334            .iter()
335            .zip(args.iter())
336            .map(|(name, ty)| (name.clone(), ty.clone()))
337            .collect();
338
339        substitute(body, &map)
340    }
341
342    /// Check that a map key type is comparable.
343    /// Only rejects concrete non-comparable types (Slice, Map, Function).
344    /// Type parameters are allowed here — they may be instantiated with comparable types.
345    /// Pre-check impl annotation for undeclared type params (e.g. `impl Container<T>`
346    /// without `impl<T>`). Adds them to scope to prevent cascading errors from
347    /// `convert_to_type`, and emits a diagnostic with the specific fix.
348    pub(crate) fn check_undeclared_impl_type_params(
349        &mut self,
350        annotation: &Annotation,
351        generics: &[Generic],
352    ) {
353        let Annotation::Constructor {
354            name: receiver_name,
355            params,
356            ..
357        } = annotation
358        else {
359            return;
360        };
361
362        let undeclared: Vec<_> = params
363            .iter()
364            .filter_map(|param| {
365                let Annotation::Constructor {
366                    name,
367                    params: sub_params,
368                    span: param_span,
369                } = param
370                else {
371                    return None;
372                };
373
374                // Single uppercase letter not declared as a type param — always a typo.
375                // Multi-letter names (Key, Error, etc.) are left to `type_not_found`.
376                if sub_params.is_empty()
377                    && name.len() == 1
378                    && name.chars().next().is_some_and(|c| c.is_uppercase())
379                    && self.lookup_generic_index(name).is_none()
380                {
381                    Some((name.to_string(), *param_span))
382                } else {
383                    None
384                }
385            })
386            .collect();
387
388        for (i, (name, param_span)) in undeclared.iter().enumerate() {
389            self.scopes
390                .current_mut()
391                .type_params
392                .get_or_insert_with(HashMap::default)
393                .insert(name.clone(), generics.len() + i);
394            self.sink
395                .push(diagnostics::infer::undeclared_impl_type_param(
396                    name,
397                    *param_span,
398                    receiver_name,
399                ));
400        }
401    }
402
403    fn check_map_key_comparable(&mut self, store: &Store, key_ty: &Type, span: Span) {
404        let resolved = key_ty.resolve_in(&self.env);
405
406        if self.is_lis(store) && resolved.resolves_to_unknown() {
407            self.sink.push(diagnostics::infer::unknown_as_map_key(span));
408            return;
409        }
410
411        let reason = if matches!(&resolved, Type::Function(_)) {
412            "functions"
413        } else if resolved.has_name("Slice") {
414            "slices"
415        } else if resolved.has_name("Map") {
416            "maps"
417        } else {
418            return;
419        };
420
421        self.sink.push(diagnostics::infer::non_comparable_map_key(
422            &resolved, reason, span,
423        ));
424    }
425}
426
427fn is_reserved_prelude_generic(name: &str) -> bool {
428    matches!(name, "Option" | "Result" | "Partial")
429}