lisette_semantics/checker/registration/
convert.rs1use 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 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 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 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 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 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 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 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}