1pub mod infer;
2mod registration;
3pub mod scopes;
4
5use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
6use std::cell::RefCell;
7use std::rc::Rc;
8
9use crate::facts::Facts;
10use crate::store::Store;
11use diagnostics::DiagnosticSink;
12use ecow::EcoString;
13use scopes::Scopes;
14use syntax::ast::Visibility as AstVisibility;
15use syntax::ast::{Annotation, Expression, Generic, ImportAlias, Span, StructFieldDefinition};
16use syntax::program::{
17 CoercionInfo, Definition, FileImport, MethodSignatures, Module, ResolutionInfo,
18};
19use syntax::types::{SubstitutionMap, Type, TypeVariableState, substitute};
20
21#[derive(Debug, Default)]
22pub struct IdGen {
23 next_type_var_id: i32,
24}
25
26impl IdGen {
27 pub fn new() -> Self {
28 Self::default()
29 }
30
31 pub fn new_type_var_id(&mut self) -> i32 {
32 let id = self.next_type_var_id;
33 self.next_type_var_id += 1;
34 id
35 }
36}
37
38#[derive(Debug, Clone)]
39pub struct Cursor {
40 pub module_id: String,
41 pub file_id: Option<u32>,
42}
43
44impl Default for Cursor {
45 fn default() -> Self {
46 Self {
47 module_id: "std".to_string(),
48 file_id: None,
49 }
50 }
51}
52
53impl Cursor {
54 pub fn new() -> Self {
55 Self::default()
56 }
57}
58
59#[derive(Debug, Default)]
60pub struct ImportState {
61 pub imported_modules: HashMap<String, (Vec<StructFieldDefinition>, Type)>,
63 pub prefix_to_module: HashMap<String, String>,
65 pub unprefixed_imports: HashSet<String>,
67}
68
69impl ImportState {
70 pub fn new() -> Self {
71 Self::default()
72 }
73
74 pub fn clear(&mut self) {
75 let prelude = self.imported_modules.remove("prelude");
77 self.imported_modules.clear();
78 if let Some(p) = prelude {
79 self.imported_modules.insert("prelude".to_string(), p);
80 }
81 let prelude_mapping = self.prefix_to_module.remove("prelude");
82 self.prefix_to_module.clear();
83 if let Some(m) = prelude_mapping {
84 self.prefix_to_module.insert("prelude".to_string(), m);
85 }
86 self.unprefixed_imports.clear();
87 }
88}
89
90type BuiltinCache = HashMap<String, Type>;
93
94pub(crate) struct InferenceState {
95 pub type_param_depth: u32,
96 pub satisfying_stack: rustc_hash::FxHashSet<(String, String)>,
97 pub inferring_assignment_target: bool,
98 pub impl_receiver_type: Option<Type>,
99 pub undo_log: Option<Vec<(Rc<RefCell<TypeVariableState>>, TypeVariableState)>>,
100 pub in_match_arm: bool,
103 pub loop_needs_label_stack: Vec<bool>,
106 pub in_subexpression: bool,
110}
111
112impl InferenceState {
113 pub fn new() -> Self {
114 Self {
115 type_param_depth: 0,
116 satisfying_stack: rustc_hash::FxHashSet::default(),
117 inferring_assignment_target: false,
118 impl_receiver_type: None,
119 undo_log: None,
120 in_match_arm: false,
121 loop_needs_label_stack: Vec::new(),
122 in_subexpression: false,
123 }
124 }
125}
126
127pub enum PostInferenceCheck {
129 GenericCall { return_ty: Type, span: Span },
131 EmptyCollection { name: String, ty: Type, span: Span },
133 StatementTail { expected_ty: Type, span: Span },
135}
136
137pub struct Checker<'r, 's> {
138 pub ids: IdGen,
139 pub store: &'r mut Store,
140 pub scopes: Scopes,
141 pub cursor: Cursor,
142 pub imports: ImportState,
143 pub builtins: BuiltinCache,
144 pub sink: &'s DiagnosticSink,
145 pub facts: Facts,
146 pub coercions: CoercionInfo,
147 pub resolutions: ResolutionInfo,
148 pub post_inference_checks: Vec<PostInferenceCheck>,
149 pub(crate) inference: InferenceState,
150 method_cache: RefCell<HashMap<EcoString, MethodSignatures>>,
151 pub ufcs_methods: HashSet<(String, String)>,
152}
153
154impl<'r, 's> Checker<'r, 's> {
155 pub fn new(store: &'r mut Store, sink: &'s DiagnosticSink) -> Self {
156 Self {
157 ids: IdGen::new(),
158 store,
159 scopes: Scopes::new(),
160 cursor: Cursor::new(),
161 imports: ImportState::new(),
162 builtins: BuiltinCache::default(),
163 sink,
164 facts: Facts::new(),
165 coercions: CoercionInfo::default(),
166 resolutions: ResolutionInfo::default(),
167 post_inference_checks: Vec::new(),
168 inference: InferenceState::new(),
169 method_cache: RefCell::new(HashMap::default()),
170 ufcs_methods: HashSet::default(),
171 }
172 }
173
174 pub fn new_type_var(&mut self) -> Type {
175 let id = self.new_type_var_id();
176 Type::Variable(Rc::new(RefCell::new(TypeVariableState::Unbound {
177 id,
178 hint: None,
179 })))
180 }
181
182 pub fn new_type_var_with_hint(&mut self, hint: &str) -> Type {
183 let id = self.new_type_var_id();
184 Type::Variable(Rc::new(RefCell::new(TypeVariableState::Unbound {
185 id,
186 hint: Some(hint.into()),
187 })))
188 }
189
190 pub fn type_from_literal_expression(&mut self, expression: &Expression) -> Option<Type> {
191 use syntax::ast::{Expression, Literal};
192 match expression {
193 Expression::Literal { literal, .. } => match literal {
194 Literal::Integer { .. } => Some(self.type_int()),
195 Literal::Float { .. } => Some(self.type_float()),
196 Literal::Boolean(_) => Some(self.type_bool()),
197 Literal::String(_) => Some(self.type_string()),
198 Literal::Char(_) => Some(self.type_char()),
199 _ => None,
200 },
201 Expression::Unary { expression, .. } => self.type_from_literal_expression(expression),
202 _ => None,
203 }
204 }
205
206 pub fn instantiate(&mut self, ty: &Type) -> (Type, SubstitutionMap) {
207 match ty {
208 Type::Forall { vars, body } => {
209 let map: SubstitutionMap = vars
210 .iter()
211 .map(|name| {
212 let id = self.new_type_var_id();
213 let fresh_var =
214 Type::Variable(Rc::new(RefCell::new(TypeVariableState::Unbound {
215 id,
216 hint: Some(name.clone()),
217 })));
218 (name.clone(), fresh_var)
219 })
220 .collect();
221
222 (substitute(body, &map), map)
223 }
224 _ => (ty.clone(), HashMap::default()),
225 }
226 }
227
228 pub fn new_file_id(&mut self) -> u32 {
229 self.store.new_file_id()
230 }
231
232 pub fn new_type_var_id(&mut self) -> i32 {
233 self.ids.new_type_var_id()
234 }
235
236 pub fn is_d_lis(&self) -> bool {
237 let Some(file_id) = self.cursor.file_id else {
238 return false;
239 };
240
241 let Some(module) = self.store.get_module(&self.cursor.module_id) else {
242 return false;
243 };
244
245 module.typedefs.contains_key(&file_id)
246 }
247
248 pub fn is_lis(&self) -> bool {
249 !self.is_d_lis()
250 }
251
252 pub(crate) fn qualify_name(&self, name: &str) -> String {
253 format!("{}.{}", self.cursor.module_id, name)
254 }
255
256 pub(crate) fn put_in_scope(&mut self, generics: &[Generic]) {
257 for (index, generic) in generics.iter().enumerate() {
258 self.scopes
259 .current_mut()
260 .type_params
261 .get_or_insert_with(HashMap::default)
262 .insert(generic.name.to_string(), index);
263 }
264 }
265
266 fn resolve_in_imported_module<'m>(
271 &self,
272 module: &'m Module,
273 simple_name: &str,
274 ) -> Option<(String, &'m Definition)> {
275 let module_prefix = format!("{}.", module.id);
276
277 let direct = format!("{}{}", module_prefix, simple_name);
279 if let Some(definition) = module.definitions.get(direct.as_str())
280 && definition.visibility().is_public()
281 {
282 return Some((direct, definition));
283 }
284
285 let suffix = format!(".{}", simple_name);
290 for (qn, definition) in &module.definitions {
291 if qn.ends_with(suffix.as_str())
292 && qn.starts_with(module_prefix.as_str())
293 && definition.visibility().is_public()
294 {
295 let rest = &qn[module_prefix.len()..];
296 if rest.contains('.') {
298 return Some((qn.to_string(), definition));
299 }
300 }
301 }
302
303 None
304 }
305
306 pub(crate) fn lookup_qualified_name(&self, type_name: &str) -> Option<String> {
307 if let Some((prefix, simple_name)) = type_name.split_once('.')
308 && let Some(module_id) = self.imports.prefix_to_module.get(prefix)
309 && let Some(imported_module) = self.store.get_module(module_id)
310 && let Some((qualified_name, _)) =
311 self.resolve_in_imported_module(imported_module, simple_name)
312 {
313 return Some(qualified_name);
314 }
315
316 let module = self.store.get_module(&self.cursor.module_id)?;
317 let qualified_name = format!("{}.{}", module.id, type_name);
318
319 if module.definitions.contains_key(qualified_name.as_str()) {
320 return Some(qualified_name);
321 }
322
323 for imported_module_id in &self.imports.unprefixed_imports {
324 if let Some(imported_module) = self.store.get_module(imported_module_id) {
325 let qualified_name = format!("{}.{}", imported_module_id, type_name);
326 if imported_module
327 .definitions
328 .contains_key(qualified_name.as_str())
329 {
330 return Some(qualified_name);
331 }
332 }
333 }
334
335 None
336 }
337
338 pub(crate) fn get_definition_name_span(&self, qualified_name: &str) -> Option<Span> {
339 self.store.get_definition(qualified_name)?.name_span()
340 }
341
342 pub(crate) fn track_name_usage(&mut self, qualified_name: &str, span: &Span, name_len: u32) {
344 if let Some(definition_span) = self.get_definition_name_span(qualified_name) {
345 let usage_span = Span::new(span.file_id, span.byte_offset, name_len);
346 self.facts.add_usage(usage_span, definition_span);
347 }
348 }
349
350 pub(crate) fn lookup_generic_index(&self, type_name: &str) -> Option<usize> {
351 self.scopes.lookup_type_param(type_name)
352 }
353
354 fn resolve_definition_value_type(&self, definition: &Definition) -> Type {
357 if let Definition::Struct {
358 constructor: Some(ctor_ty),
359 ..
360 } = definition
361 {
362 return ctor_ty.clone();
363 }
364
365 if let Definition::TypeAlias { ty: alias_ty, .. } = definition {
367 let underlying = match alias_ty {
368 Type::Forall { body, .. } => body.as_ref(),
369 other => other,
370 };
371 if let Type::Constructor { id, .. } = underlying
372 && let Some(Definition::Struct {
373 constructor: Some(ctor_ty),
374 ..
375 }) = self.store.get_definition(id)
376 {
377 return ctor_ty.clone();
378 }
379 }
380
381 definition.ty().clone()
382 }
383
384 pub(crate) fn lookup_type(&self, value_name: &str) -> Option<Type> {
385 if let Some(ty) = self.scopes.lookup_value(value_name) {
386 return Some(ty.clone());
387 }
388
389 if let Some((_definition, ty)) = self.imports.imported_modules.get(value_name) {
390 return Some(ty.clone());
391 }
392
393 if let Some((prefix, rest)) = value_name.split_once('.')
394 && let Some(module_id) = self.imports.prefix_to_module.get(prefix)
395 && let Some(imported_module) = self.store.get_module(module_id)
396 && let Some((_, definition)) = self.resolve_in_imported_module(imported_module, rest)
397 {
398 return Some(self.resolve_definition_value_type(definition));
399 }
400
401 let module = self.store.get_module(&self.cursor.module_id)?;
402 let qualified_name = format!("{}.{}", module.id, value_name);
403
404 if let Some(definition) = module.definitions.get(qualified_name.as_str()) {
405 return Some(self.resolve_definition_value_type(definition));
406 }
407
408 for imported_module_id in &self.imports.unprefixed_imports {
409 if let Some(imported_module) = self.store.get_module(imported_module_id) {
410 let qualified_name = format!("{}.{}", imported_module_id, value_name);
411 if let Some(definition) = imported_module.definitions.get(qualified_name.as_str()) {
412 return Some(self.resolve_definition_value_type(definition));
413 }
414 }
415 }
416
417 None
418 }
419
420 pub(crate) fn is_enum_type(&self, ty: &Type) -> bool {
421 let Type::Constructor { id, .. } = ty else {
422 return false;
423 };
424 let Some(definition) = self.store.get_definition(id) else {
425 return false;
426 };
427 matches!(
428 definition,
429 Definition::Enum { .. } | Definition::ValueEnum { .. }
430 )
431 }
432
433 pub(crate) fn resolve_type_name(&mut self, type_name: &str) -> Option<(String, Type)> {
434 if self.scopes.lookup_type_param(type_name).is_some() {
435 return None;
436 }
437
438 let qualified_name = self.lookup_qualified_name(type_name)?;
439 let ty = self.store.get_type(&qualified_name)?.clone();
440
441 Some((qualified_name, ty))
442 }
443
444 pub(crate) fn resolve_type_from_prelude(&self, type_name: &str) -> Option<(String, Type)> {
445 let qualified_name = format!("prelude.{}", type_name);
446 let ty = self.store.get_type(&qualified_name)?.clone();
447 Some((qualified_name, ty))
448 }
449
450 pub(crate) fn get_all_methods(&self, ty: &Type) -> MethodSignatures {
451 if let Type::Parameter(name) = ty {
452 let trait_bounds = self.scopes.collect_all_trait_bounds();
453 let qualified_name = self.qualify_name(name);
454 return self
455 .store
456 .get_methods_from_bounds(&qualified_name, &trait_bounds);
457 }
458
459 let Type::Constructor { id, .. } = ty.strip_refs().resolve() else {
460 return MethodSignatures::default();
461 };
462
463 if self.store.get_interface(&id).is_some() {
465 let empty = HashMap::default();
466 return self.store.get_all_methods(ty, &empty);
467 }
468
469 if let Some(cached) = self.method_cache.borrow().get(&id) {
470 return cached.clone();
471 }
472
473 let empty = HashMap::default();
474 let methods = self.store.get_all_methods(ty, &empty);
475 self.method_cache.borrow_mut().insert(id, methods.clone());
476 methods
477 }
478
479 pub fn reset_scopes(&mut self) {
480 self.scopes.reset();
481 self.imports.clear();
482 }
483
484 pub fn failed(&self) -> bool {
485 self.sink.has_errors()
486 }
487
488 pub fn put_prelude_in_scope(&mut self) {
489 self.put_unprefixed_module_in_scope("prelude");
490 if self.imports.imported_modules.contains_key("prelude") {
491 return;
492 }
493 self.put_module_in_scope("prelude", Some("prelude".to_string()));
494 }
495
496 pub fn put_unprefixed_module_in_scope(&mut self, module_id: &str) {
497 self.put_module_in_scope(module_id, None)
498 }
499
500 pub fn put_imported_modules_in_scope(&mut self, imports: &[FileImport]) {
501 let mut seen_aliases: HashMap<String, String> = HashMap::default(); let mut seen_paths: HashSet<String> = HashSet::default();
503
504 for import in imports {
505 if seen_paths.contains(import.name.as_str()) {
506 self.sink.push(diagnostics::infer::duplicate_import_path(
507 &import.name,
508 import.name_span,
509 ));
510 continue;
511 }
512 seen_paths.insert(import.name.to_string());
513
514 if let Some(ImportAlias::Blank(blank_span)) = &import.alias {
515 if !import.name.starts_with("go:") {
516 self.sink
517 .push(diagnostics::infer::blank_import_non_go(*blank_span));
518 }
519 continue;
520 }
521
522 if let Some(ImportAlias::Named(alias, alias_span)) = &import.alias
523 && is_reserved_import_alias(alias)
524 {
525 self.sink.push(diagnostics::infer::reserved_import_alias(
526 alias,
527 *alias_span,
528 ));
529 continue;
530 }
531
532 let Some(effective) = import.effective_alias() else {
533 continue;
534 };
535
536 if let Some(existing_path) = seen_aliases.get(&effective)
537 && existing_path != &import.name
538 {
539 self.sink.push(diagnostics::infer::import_conflict(
540 &effective,
541 existing_path,
542 &import.name,
543 import.name_span,
544 ));
545 continue;
546 }
547
548 seen_aliases.insert(effective.clone(), import.name.to_string());
549
550 if !self.store.has(&import.name) {
551 continue;
552 }
553
554 self.put_module_in_scope(&import.name, Some(effective));
555 }
556 }
557
558 pub fn put_module_in_scope(&mut self, module_id: &str, prefix: Option<String>) {
559 let Some(prefix) = prefix else {
560 self.imports
561 .unprefixed_imports
562 .insert(module_id.to_string());
563 return;
564 };
565
566 let module = self
567 .store
568 .get_module(module_id)
569 .expect("module must exist when putting in scope");
570
571 let imported_module_id = module.id.clone();
572 let module_prefix = format!("{}.", module.id);
573
574 let module_struct_fields: Vec<_> = module
575 .definitions
576 .iter()
577 .filter(|(qn, _)| module.is_public(qn))
578 .filter(|(qn, _)| {
579 qn.strip_prefix(&module_prefix)
580 .is_some_and(|rest| !rest.contains('.'))
581 })
582 .map(|(qn, definition)| {
583 let simple_name = qn
584 .strip_prefix(&module_prefix)
585 .expect("qualified_name must start with module prefix");
586 let ty = if let Definition::Struct {
587 constructor: Some(ctor_ty),
588 ..
589 } = definition
590 {
591 ctor_ty.clone()
592 } else {
593 definition.ty().clone()
594 };
595 StructFieldDefinition {
596 doc: None,
597 attributes: vec![],
598 visibility: AstVisibility::Public,
599 name: simple_name.into(),
600 name_span: Span::dummy(),
601 annotation: Annotation::Unknown,
602 ty,
603 }
604 })
605 .collect();
606
607 let ty = Type::Constructor {
608 id: format!("@import/{}", imported_module_id).into(),
609 params: vec![],
610 underlying_ty: None,
611 };
612
613 self.imports
614 .imported_modules
615 .insert(prefix.clone(), (module_struct_fields, ty));
616 self.imports
617 .prefix_to_module
618 .insert(prefix, imported_module_id);
619 }
620
621 pub(crate) fn speculatively<T, E>(
624 &mut self,
625 f: impl FnOnce(&mut Self) -> Result<T, E>,
626 ) -> Result<T, E> {
627 let prev_log = self.inference.undo_log.take();
628 self.inference.undo_log = Some(Vec::new());
629 let result = f(self);
630 let log = self.inference.undo_log.take().unwrap();
631 self.inference.undo_log = prev_log;
632 if result.is_err() {
633 for (type_var, original_state) in log.into_iter().rev() {
634 *type_var.borrow_mut() = original_state;
635 }
636 } else if let Some(parent_log) = &mut self.inference.undo_log {
637 parent_log.extend(log);
638 }
639 result
640 }
641
642 pub(crate) fn set_inferring_assignment_target(&mut self) {
643 self.inference.inferring_assignment_target = true;
644 }
645
646 pub(crate) fn clear_inferring_assignment_target(&mut self) {
647 self.inference.inferring_assignment_target = false;
648 }
649
650 pub(crate) fn is_inferring_assignment_target(&self) -> bool {
651 self.inference.inferring_assignment_target
652 }
653}
654
655fn is_reserved_import_alias(name: &str) -> bool {
660 matches!(
661 name,
662 "break"
664 | "case"
665 | "chan"
666 | "const"
667 | "continue"
668 | "default"
669 | "defer"
670 | "else"
671 | "fallthrough"
672 | "for"
673 | "func"
674 | "go"
675 | "goto"
676 | "if"
677 | "interface"
678 | "map"
679 | "package"
680 | "range"
681 | "return"
682 | "select"
683 | "struct"
684 | "switch"
685 | "type"
686 | "var"
687 | "nil"
689 | "iota"
690 | "true"
691 | "false"
692 | "bool"
694 | "byte"
695 | "complex64"
696 | "complex128"
697 | "error"
698 | "float32"
699 | "float64"
700 | "int"
701 | "int8"
702 | "int16"
703 | "int32"
704 | "int64"
705 | "rune"
706 | "string"
707 | "uint"
708 | "uint8"
709 | "uint16"
710 | "uint32"
711 | "uint64"
712 | "uintptr"
713 | "append"
715 | "cap"
716 | "clear"
717 | "close"
718 | "complex"
719 | "copy"
720 | "delete"
721 | "imag"
722 | "len"
723 | "make"
724 | "max"
725 | "min"
726 | "new"
727 | "panic"
728 | "print"
729 | "println"
730 | "real"
731 | "recover"
732 | "any"
734 | "comparable"
735 | "init"
737 | "main"
738 | "Option"
740 | "Result"
741 | "Some"
742 | "None"
743 | "Ok"
744 | "Err"
745 )
746}