Skip to main content

lisette_semantics/checker/
mod.rs

1pub mod freeze;
2pub mod infer;
3pub(crate) mod registration;
4pub mod scopes;
5pub mod type_env;
6
7use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
8use std::cell::RefCell;
9use std::sync::Arc;
10
11use crate::facts::{BindingIdAllocator, Facts};
12use crate::store::Store;
13use diagnostics::LocalSink;
14use ecow::EcoString;
15use scopes::Scopes;
16use syntax::ast::Visibility as AstVisibility;
17use syntax::ast::{Annotation, Expression, Generic, ImportAlias, Span, StructFieldDefinition};
18use syntax::program::{Definition, DefinitionBody, File, FileImport, MethodSignatures, Module};
19use syntax::types::{SubstitutionMap, Symbol, Type, substitute};
20
21pub use type_env::{EnvResolve, Speculation, TypeEnv, VarState};
22
23#[derive(Debug, Clone)]
24pub struct Cursor {
25    pub module_id: String,
26    pub file_id: Option<u32>,
27}
28
29impl Default for Cursor {
30    fn default() -> Self {
31        Self {
32            module_id: "std".to_string(),
33            file_id: None,
34        }
35    }
36}
37
38impl Cursor {
39    pub fn new() -> Self {
40        Self::default()
41    }
42}
43
44#[derive(Debug, Default)]
45pub struct ImportState {
46    /// Module prefix -> (struct fields, module type)
47    pub imported_modules: HashMap<String, (Vec<StructFieldDefinition>, Type)>,
48    /// Import prefix -> actual module_id in Store (e.g., "http" -> "go:net/http")
49    pub prefix_to_module: HashMap<String, String>,
50    /// Modules whose exports are available without prefix (current module and prelude)
51    pub unprefixed_imports: HashSet<String>,
52    /// Effective aliases (e.g. `mux`) of imports whose underlying module
53    /// failed to load (missing typedef, undeclared, module_not_found, etc.).
54    pub failed_imports: HashSet<String>,
55}
56
57impl ImportState {
58    pub fn new() -> Self {
59        Self::default()
60    }
61
62    pub fn clear(&mut self) {
63        // Preserve prelude entries since they never change
64        let prelude = self.imported_modules.remove("prelude");
65        self.imported_modules.clear();
66        if let Some(p) = prelude {
67            self.imported_modules.insert("prelude".to_string(), p);
68        }
69        let prelude_mapping = self.prefix_to_module.remove("prelude");
70        self.prefix_to_module.clear();
71        if let Some(m) = prelude_mapping {
72            self.prefix_to_module.insert("prelude".to_string(), m);
73        }
74        self.unprefixed_imports.clear();
75        self.failed_imports.clear();
76    }
77}
78
79#[derive(Debug, Clone, Copy)]
80pub(crate) enum FileContextKind {
81    Standard,
82    ImportedTypedef,
83    Prelude,
84}
85
86struct SavedFileContext {
87    file_id: Option<u32>,
88    scopes: Scopes,
89    imports: ImportState,
90}
91
92/// Cache for builtin types (int, bool, string, etc.) resolved from the prelude.
93/// These never change once populated, so no invalidation needed.
94type BuiltinCache = HashMap<String, Type>;
95
96/// Per-task mutable state. Paired with `AnalysisContext` (shared read-only view).
97pub struct TaskState<'s> {
98    pub env: TypeEnv,
99    pub scopes: Scopes,
100    pub cursor: Cursor,
101    pub imports: ImportState,
102    pub builtins: BuiltinCache,
103    pub sink: &'s LocalSink,
104    pub facts: Facts,
105    /// Recursion guard for interface satisfaction. Prevents
106    /// `collect_interface_violations` from diverging when a bound on `T`
107    /// transitively requires checking `T` against the same interface.
108    pub satisfying_stack: rustc_hash::FxHashSet<(String, String)>,
109    method_cache: RefCell<HashMap<EcoString, MethodSignatures>>,
110    pub ufcs_methods: HashSet<(String, String)>,
111    /// Typed files produced by inference.
112    pub typed_files: Vec<(String, File)>,
113    /// Reentrancy counter: > 0 while resolving a generic bound annotation.
114    /// Lets `convert_to_type` admit bound-only markers (e.g. `Comparable`)
115    /// without flagging them as misuse in value positions.
116    pub bound_position_depth: u32,
117}
118
119impl<'s> TaskState<'s> {
120    pub fn new(sink: &'s LocalSink, binding_ids: Arc<BindingIdAllocator>) -> Self {
121        Self {
122            env: TypeEnv::new(),
123            scopes: Scopes::new(),
124            cursor: Cursor::new(),
125            imports: ImportState::new(),
126            builtins: BuiltinCache::default(),
127            sink,
128            facts: Facts::new(binding_ids),
129            satisfying_stack: rustc_hash::FxHashSet::default(),
130            method_cache: RefCell::new(HashMap::default()),
131            ufcs_methods: HashSet::default(),
132            typed_files: Vec::new(),
133            bound_position_depth: 0,
134        }
135    }
136
137    pub fn with_fresh_allocator(sink: &'s LocalSink) -> Self {
138        Self::new(sink, Arc::new(BindingIdAllocator::new()))
139    }
140
141    pub fn new_type_var(&mut self) -> Type {
142        let id = self.env.fresh(None);
143        Type::Var { id, hint: None }
144    }
145
146    pub fn new_type_var_with_hint(&mut self, hint: &str) -> Type {
147        let hint: EcoString = hint.into();
148        let id = self.env.fresh(Some(hint.clone()));
149        Type::Var {
150            id,
151            hint: Some(hint),
152        }
153    }
154
155    pub fn type_from_literal_expression(&mut self, expression: &Expression) -> Option<Type> {
156        use syntax::ast::{Expression, Literal};
157        match expression {
158            Expression::Literal { literal, .. } => match literal {
159                Literal::Integer { .. } => Some(self.type_int()),
160                Literal::Float { .. } => Some(self.type_float()),
161                Literal::Boolean(_) => Some(self.type_bool()),
162                Literal::String { .. } => Some(self.type_string()),
163                Literal::Char(_) => Some(self.type_char()),
164                _ => None,
165            },
166            Expression::Unary { expression, .. } => self.type_from_literal_expression(expression),
167            _ => None,
168        }
169    }
170
171    pub fn instantiate(&mut self, ty: &Type) -> (Type, SubstitutionMap) {
172        match ty {
173            Type::Forall { vars, body } => {
174                let map: SubstitutionMap = vars
175                    .iter()
176                    .map(|name| {
177                        let id = self.env.fresh(Some(name.clone()));
178                        let fresh_var = Type::Var {
179                            id,
180                            hint: Some(name.clone()),
181                        };
182                        (name.clone(), fresh_var)
183                    })
184                    .collect();
185
186                (substitute(body, &map), map)
187            }
188            _ => (ty.clone(), HashMap::default()),
189        }
190    }
191
192    pub fn new_file_id(&mut self, store: &Store) -> u32 {
193        store.new_file_id()
194    }
195
196    pub fn is_d_lis(&self, store: &Store) -> bool {
197        let Some(file_id) = self.cursor.file_id else {
198            return false;
199        };
200
201        let Some(module) = store.get_module(&self.cursor.module_id) else {
202            return false;
203        };
204
205        module.typedefs.contains_key(&file_id)
206    }
207
208    pub fn is_lis(&self, store: &Store) -> bool {
209        !self.is_d_lis(store)
210    }
211
212    pub(crate) fn current_module<'a>(&self, store: &'a Store) -> &'a Module {
213        store
214            .get_module(&self.cursor.module_id)
215            .expect("current module must exist in store")
216    }
217
218    pub(crate) fn current_module_mut<'a>(&self, store: &'a mut Store) -> &'a mut Module {
219        store
220            .get_module_mut(&self.cursor.module_id)
221            .expect("current module must exist in store")
222    }
223
224    pub(crate) fn qualify_name(&self, name: &str) -> Symbol {
225        Symbol::from_parts(&self.cursor.module_id, name)
226    }
227
228    pub(crate) fn put_in_scope(&mut self, generics: &[Generic]) {
229        for (index, generic) in generics.iter().enumerate() {
230            self.scopes
231                .current_mut()
232                .type_params
233                .get_or_insert_with(HashMap::default)
234                .insert(generic.name.to_string(), index);
235        }
236    }
237
238    /// Validate that all bound annotations on generics refer to types that exist in scope.
239    pub(crate) fn validate_generic_bounds(
240        &mut self,
241        store: &Store,
242        generics: &[Generic],
243        span: &Span,
244    ) {
245        for g in generics {
246            for b in &g.bounds {
247                self.register_bound_annotation(store, b, span);
248            }
249        }
250    }
251
252    pub(crate) fn register_bound_annotation(
253        &mut self,
254        store: &Store,
255        bound: &Annotation,
256        span: &Span,
257    ) -> Type {
258        let resolved = self.convert_bound_to_type(store, bound, span);
259        if self.is_lis(store) && resolved.contains_unknown() {
260            self.sink
261                .push(diagnostics::infer::unknown_in_bound_position(
262                    bound.get_span(),
263                ));
264        }
265        resolved
266    }
267
268    /// Resolve a simple name (e.g., "Sunday") to a public definition in an imported module.
269    /// First tries direct match (`module_id.name`), then falls back to searching
270    /// for nested definitions (e.g., `module_id.Weekday.Sunday`) preferring top-level
271    /// over nested when both share the same simple name.
272    fn resolve_in_imported_module<'m>(
273        &self,
274        module: &'m Module,
275        simple_name: &str,
276    ) -> Option<(String, &'m Definition)> {
277        let module_prefix = format!("{}.", module.id);
278
279        // Direct match: module_id.simple_name
280        let direct = format!("{}{}", module_prefix, simple_name);
281        if let Some(definition) = module.definitions.get(direct.as_str())
282            && definition.visibility().is_public()
283        {
284            return Some((direct, definition));
285        }
286
287        // Nested match: find a public definition whose simple name matches,
288        // e.g., module_id.EnumType.VariantName where simple_name = "VariantName".
289        // Skip if a top-level definition with the same simple name exists
290        // (handles transitive import collisions like go:net/http).
291        let suffix = format!(".{}", simple_name);
292        for (qn, definition) in &module.definitions {
293            if qn.ends_with(suffix.as_str())
294                && qn.starts_with(module_prefix.as_str())
295                && definition.visibility().is_public()
296            {
297                let rest = &qn[module_prefix.len()..];
298                // Only match if it's nested (contains a dot) — direct was tried above
299                if rest.contains('.') {
300                    return Some((qn.to_string(), definition));
301                }
302            }
303        }
304
305        None
306    }
307
308    pub(crate) fn lookup_qualified_name(&self, store: &Store, type_name: &str) -> Option<String> {
309        if let Some((prefix, simple_name)) = type_name.split_once('.')
310            && let Some(module_id) = self.imports.prefix_to_module.get(prefix)
311            && let Some(imported_module) = store.get_module(module_id)
312            && let Some((qualified_name, _)) =
313                self.resolve_in_imported_module(imported_module, simple_name)
314        {
315            return Some(qualified_name);
316        }
317
318        let module = store.get_module(&self.cursor.module_id)?;
319        let qualified_name = Symbol::from_parts(&module.id, type_name);
320
321        if module.definitions.contains_key(qualified_name.as_str()) {
322            return Some(qualified_name.to_string());
323        }
324
325        for imported_module_id in &self.imports.unprefixed_imports {
326            if let Some(imported_module) = store.get_module(imported_module_id) {
327                let qualified_name = Symbol::from_parts(imported_module_id, type_name);
328                if imported_module
329                    .definitions
330                    .contains_key(qualified_name.as_str())
331                {
332                    return Some(qualified_name.to_string());
333                }
334            }
335        }
336
337        None
338    }
339
340    pub(crate) fn get_definition_name_span(
341        &self,
342        store: &Store,
343        qualified_name: &str,
344    ) -> Option<Span> {
345        store.get_definition(qualified_name)?.name_span()
346    }
347
348    pub(crate) fn is_const_name(&self, store: &Store, qualified_name: &str) -> bool {
349        if qualified_name.starts_with("go:") {
350            return false;
351        }
352        store
353            .module_for_qualified_name(qualified_name)
354            .and_then(|module_id| store.get_module(module_id))
355            .is_some_and(|module| module.const_names.contains(qualified_name))
356    }
357
358    pub(crate) fn is_const_var(&self, store: &Store, var_name: &str) -> bool {
359        if self.scopes.lookup_binding_id(var_name).is_some() {
360            return false;
361        }
362        if self.scopes.lookup_const(var_name) {
363            return true;
364        }
365        self.lookup_qualified_name(store, var_name)
366            .is_some_and(|qname| self.is_const_name(store, &qname))
367    }
368
369    /// Track that `name` (at the start of `span`) refers to the definition at `qualified_name`.
370    pub(crate) fn track_name_usage(
371        &mut self,
372        store: &Store,
373        qualified_name: &str,
374        span: &Span,
375        name_len: u32,
376    ) {
377        if let Some(definition_span) = self.get_definition_name_span(store, qualified_name) {
378            let usage_span = Span::new(span.file_id, span.byte_offset, name_len);
379            self.facts.add_usage(usage_span, definition_span);
380        }
381    }
382
383    pub(crate) fn lookup_generic_index(&self, type_name: &str) -> Option<usize> {
384        self.scopes.lookup_type_param(type_name)
385    }
386
387    /// Resolves the value type for a definition. Returns the constructor type for
388    /// structs with constructors (tuple structs) and for type aliases pointing to them.
389    fn resolve_definition_value_type(&self, store: &Store, definition: &Definition) -> Type {
390        if let DefinitionBody::Struct {
391            constructor: Some(ctor_ty),
392            ..
393        } = &definition.body
394        {
395            return ctor_ty.clone();
396        }
397
398        // Type alias to tuple struct should return constructor type.
399        if let DefinitionBody::TypeAlias { .. } = &definition.body {
400            let alias_ty = &definition.ty;
401            let underlying = match alias_ty {
402                Type::Forall { body, .. } => body.as_ref(),
403                other => other,
404            };
405            if let Type::Nominal { id, .. } = underlying
406                && let Some(Definition {
407                    body:
408                        DefinitionBody::Struct {
409                            constructor: Some(ctor_ty),
410                            ..
411                        },
412                    ..
413                }) = store.get_definition(id)
414            {
415                return ctor_ty.clone();
416            }
417        }
418
419        definition.ty().clone()
420    }
421
422    pub(crate) fn lookup_type(&self, store: &Store, value_name: &str) -> Option<Type> {
423        if let Some(ty) = self.scopes.lookup_value(value_name) {
424            return Some(ty.clone());
425        }
426
427        if let Some((_definition, ty)) = self.imports.imported_modules.get(value_name) {
428            return Some(ty.clone());
429        }
430
431        if let Some((prefix, rest)) = value_name.split_once('.')
432            && let Some(module_id) = self.imports.prefix_to_module.get(prefix)
433            && let Some(imported_module) = store.get_module(module_id)
434            && let Some((_, definition)) = self.resolve_in_imported_module(imported_module, rest)
435        {
436            return Some(self.resolve_definition_value_type(store, definition));
437        }
438
439        let module = store.get_module(&self.cursor.module_id)?;
440        let qualified_name = Symbol::from_parts(&module.id, value_name);
441
442        if let Some(definition) = module.definitions.get(qualified_name.as_str()) {
443            return Some(self.resolve_definition_value_type(store, definition));
444        }
445
446        for imported_module_id in &self.imports.unprefixed_imports {
447            if let Some(imported_module) = store.get_module(imported_module_id) {
448                let qualified_name = Symbol::from_parts(imported_module_id, value_name);
449                if let Some(definition) = imported_module.definitions.get(qualified_name.as_str()) {
450                    return Some(self.resolve_definition_value_type(store, definition));
451                }
452            }
453        }
454
455        None
456    }
457
458    pub(crate) fn is_enum_type(&self, store: &Store, ty: &Type) -> bool {
459        let Type::Nominal { id, .. } = ty else {
460            return false;
461        };
462        let Some(definition) = store.get_definition(id) else {
463            return false;
464        };
465        matches!(
466            definition.body,
467            DefinitionBody::Enum { .. } | DefinitionBody::ValueEnum { .. }
468        )
469    }
470
471    pub(crate) fn resolve_type_name(
472        &mut self,
473        store: &Store,
474        type_name: &str,
475    ) -> Option<(String, Type)> {
476        if self.scopes.lookup_type_param(type_name).is_some() {
477            return None;
478        }
479
480        let qualified_name = self.lookup_qualified_name(store, type_name)?;
481        let ty = store.get_type(&qualified_name)?.clone();
482
483        Some((qualified_name, ty))
484    }
485
486    pub(crate) fn resolve_type_from_prelude(
487        &self,
488        store: &Store,
489        type_name: &str,
490    ) -> Option<(String, Type)> {
491        let qualified_name = format!("prelude.{}", type_name);
492        let ty = store.get_type(&qualified_name)?.clone();
493        Some((qualified_name, ty))
494    }
495
496    pub(crate) fn get_all_methods(&self, store: &Store, ty: &Type) -> MethodSignatures {
497        if let Type::Parameter(name) = ty {
498            let trait_bounds = self.scopes.collect_all_trait_bounds();
499            let qualified_name = self.qualify_name(name);
500            return store.get_methods_from_bounds(&qualified_name, &trait_bounds);
501        }
502
503        let resolved = ty.strip_refs().resolve_in(&self.env);
504        let cache_key: EcoString = match &resolved {
505            Type::Nominal { id, .. } => id.as_eco().clone(),
506            Type::Compound { kind, .. } => format!("prelude.{}", kind.leaf_name()).into(),
507            Type::Simple(kind) => format!("prelude.{}", kind.leaf_name()).into(),
508            _ => return MethodSignatures::default(),
509        };
510
511        // Interfaces need type-arg-dependent generic substitution, skip cache.
512        let peeled = store.peel_alias(&resolved);
513        if let Type::Nominal { id: peeled_id, .. } = &peeled
514            && store.get_interface(peeled_id).is_some()
515        {
516            let empty = HashMap::default();
517            return store.get_all_methods(&peeled, &empty);
518        }
519
520        if let Some(cached) = self.method_cache.borrow().get(cache_key.as_str()) {
521            return cached.clone();
522        }
523
524        let empty = HashMap::default();
525        // Pass the env-resolved type so the store's env-less `resolve()` stays
526        // identity-safe: `Type::Var` chains are chased once here rather than
527        // silently returning empty methods in the store.
528        let methods = store.get_all_methods(&resolved, &empty);
529        self.method_cache
530            .borrow_mut()
531            .insert(cache_key, methods.clone());
532        methods
533    }
534
535    pub fn reset_scopes(&mut self) {
536        self.scopes.reset();
537        self.imports.clear();
538    }
539
540    pub(crate) fn with_module_cursor<T>(
541        &mut self,
542        module_id: &str,
543        f: impl FnOnce(&mut Self) -> T,
544    ) -> T {
545        if self.cursor.module_id == module_id {
546            return f(self);
547        }
548
549        let previous_module_id = std::mem::replace(&mut self.cursor.module_id, module_id.into());
550        let result = f(self);
551        self.cursor.module_id = previous_module_id;
552        result
553    }
554
555    pub(crate) fn with_file_context<T>(
556        &mut self,
557        store: &Store,
558        module_id: &str,
559        file_id: u32,
560        imports: &[FileImport],
561        kind: FileContextKind,
562        f: impl FnOnce(&mut Self, &Store) -> T,
563    ) -> T {
564        self.with_module_cursor(module_id, |this| {
565            let saved = this.enter_file_context(store, module_id, file_id, imports, kind);
566            let result = f(this, store);
567            this.exit_file_context(saved);
568            result
569        })
570    }
571
572    pub(crate) fn with_file_context_mut<T>(
573        &mut self,
574        store: &mut Store,
575        module_id: &str,
576        file_id: u32,
577        imports: &[FileImport],
578        kind: FileContextKind,
579        f: impl FnOnce(&mut Self, &mut Store) -> T,
580    ) -> T {
581        self.with_module_cursor(module_id, |this| {
582            let saved = this.enter_file_context(&*store, module_id, file_id, imports, kind);
583            let result = f(this, store);
584            this.exit_file_context(saved);
585            result
586        })
587    }
588
589    fn enter_file_context(
590        &mut self,
591        store: &Store,
592        module_id: &str,
593        file_id: u32,
594        imports: &[FileImport],
595        kind: FileContextKind,
596    ) -> SavedFileContext {
597        let saved = SavedFileContext {
598            file_id: self.cursor.file_id.replace(file_id),
599            scopes: std::mem::take(&mut self.scopes),
600            imports: std::mem::take(&mut self.imports),
601        };
602
603        match kind {
604            FileContextKind::Standard => {
605                self.put_prelude_in_scope(store);
606                self.put_unprefixed_module_in_scope(store, module_id);
607            }
608            FileContextKind::ImportedTypedef => {
609                self.put_prelude_in_scope(store);
610            }
611            FileContextKind::Prelude => {
612                self.put_unprefixed_module_in_scope(store, module_id);
613            }
614        }
615        self.put_imported_modules_in_scope(store, imports);
616
617        saved
618    }
619
620    fn exit_file_context(&mut self, saved: SavedFileContext) {
621        self.scopes = saved.scopes;
622        self.imports = saved.imports;
623        self.cursor.file_id = saved.file_id;
624    }
625
626    pub fn failed(&self) -> bool {
627        self.sink.has_errors()
628    }
629
630    pub fn put_prelude_in_scope(&mut self, store: &Store) {
631        self.put_unprefixed_module_in_scope(store, "prelude");
632        if self.imports.imported_modules.contains_key("prelude") {
633            return;
634        }
635        self.put_module_in_scope(store, "prelude", Some("prelude".to_string()));
636    }
637
638    pub fn put_unprefixed_module_in_scope(&mut self, store: &Store, module_id: &str) {
639        self.put_module_in_scope(store, module_id, None)
640    }
641
642    pub fn put_imported_modules_in_scope(&mut self, store: &Store, imports: &[FileImport]) {
643        let mut seen_aliases: HashMap<String, String> = HashMap::default(); // alias -> path
644        let mut seen_paths: HashSet<String> = HashSet::default();
645
646        for import in imports {
647            if seen_paths.contains(import.name.as_str()) {
648                self.sink.push(diagnostics::infer::duplicate_import_path(
649                    &import.name,
650                    import.name_span,
651                ));
652                continue;
653            }
654            seen_paths.insert(import.name.to_string());
655
656            if matches!(import.alias, Some(ImportAlias::Blank(_))) {
657                continue;
658            }
659
660            if let Some(ImportAlias::Named(alias, alias_span)) = &import.alias
661                && is_reserved_import_alias(alias)
662            {
663                self.sink.push(diagnostics::infer::reserved_import_alias(
664                    alias,
665                    *alias_span,
666                ));
667                continue;
668            }
669
670            let Some(effective) = import.effective_alias(&store.go_package_names) else {
671                continue;
672            };
673
674            if let Some(existing_path) = seen_aliases.get(&effective)
675                && existing_path != &import.name
676            {
677                self.sink.push(diagnostics::infer::import_conflict(
678                    &effective,
679                    existing_path,
680                    &import.name,
681                    import.name_span,
682                ));
683                continue;
684            }
685
686            seen_aliases.insert(effective.clone(), import.name.to_string());
687
688            let module = store.get_module(&import.name);
689            if module.is_none() || module.is_some_and(Module::is_empty_stub) {
690                self.imports.failed_imports.insert(effective);
691                continue;
692            }
693
694            self.put_module_in_scope(store, &import.name, Some(effective));
695        }
696    }
697
698    pub fn put_module_in_scope(&mut self, store: &Store, module_id: &str, prefix: Option<String>) {
699        let Some(prefix) = prefix else {
700            self.imports
701                .unprefixed_imports
702                .insert(module_id.to_string());
703            return;
704        };
705
706        let module = store
707            .get_module(module_id)
708            .expect("module must exist when putting in scope");
709
710        let imported_module_id = module.id.clone();
711        let module_prefix = format!("{}.", module.id);
712
713        let module_struct_fields: Vec<_> = module
714            .definitions
715            .iter()
716            .filter(|(qn, _)| module.is_public(qn))
717            .filter(|(qn, _)| {
718                qn.strip_prefix(&module_prefix)
719                    .is_some_and(|rest| !rest.contains('.'))
720            })
721            .map(|(qn, definition)| {
722                let simple_name = qn
723                    .strip_prefix(&module_prefix)
724                    .expect("qualified_name must start with module prefix");
725                let ty = if let DefinitionBody::Struct {
726                    constructor: Some(ctor_ty),
727                    ..
728                } = &definition.body
729                {
730                    ctor_ty.clone()
731                } else {
732                    definition.ty().clone()
733                };
734                StructFieldDefinition {
735                    doc: None,
736                    attributes: vec![],
737                    visibility: AstVisibility::Public,
738                    name: simple_name.into(),
739                    name_span: Span::dummy(),
740                    annotation: Annotation::Unknown,
741                    ty,
742                }
743            })
744            .collect();
745
746        let ty = Type::ImportNamespace(imported_module_id.clone().into());
747
748        self.imports
749            .imported_modules
750            .insert(prefix.clone(), (module_struct_fields, ty));
751        self.imports
752            .prefix_to_module
753            .insert(prefix, imported_module_id);
754    }
755
756    /// Run a closure speculatively: if it returns `Err`, all type variable
757    /// bindings performed during the closure are rolled back.
758    pub(crate) fn speculatively<T, E>(
759        &mut self,
760        f: impl FnOnce(&mut Self) -> Result<T, E>,
761    ) -> Result<T, E> {
762        let spec = self.env.begin_speculation();
763        let result = f(self);
764        self.env.end_speculation(spec, result.is_err());
765        result
766    }
767}
768
769/// Returns `true` if the given name is reserved and cannot be used as an import alias.
770///
771/// Reserved names include Go keywords, Go predeclared identifiers, Go builtins,
772/// Go type constraint names, and Lisette prelude symbols.
773fn is_reserved_import_alias(name: &str) -> bool {
774    matches!(
775        name,
776        // Go keywords
777        "break"
778        | "case"
779        | "chan"
780        | "const"
781        | "continue"
782        | "default"
783        | "defer"
784        | "else"
785        | "fallthrough"
786        | "for"
787        | "func"
788        | "go"
789        | "goto"
790        | "if"
791        | "interface"
792        | "map"
793        | "package"
794        | "range"
795        | "return"
796        | "select"
797        | "struct"
798        | "switch"
799        | "type"
800        | "var"
801        // Go predeclared identifiers
802        | "nil"
803        | "iota"
804        | "true"
805        | "false"
806        // Go predeclared types
807        | "bool"
808        | "byte"
809        | "complex64"
810        | "complex128"
811        | "error"
812        | "float32"
813        | "float64"
814        | "int"
815        | "int8"
816        | "int16"
817        | "int32"
818        | "int64"
819        | "rune"
820        | "string"
821        | "uint"
822        | "uint8"
823        | "uint16"
824        | "uint32"
825        | "uint64"
826        | "uintptr"
827        // Go builtins
828        | "append"
829        | "cap"
830        | "clear"
831        | "close"
832        | "complex"
833        | "copy"
834        | "delete"
835        | "imag"
836        | "len"
837        | "make"
838        | "max"
839        | "min"
840        | "new"
841        | "panic"
842        | "print"
843        | "println"
844        | "real"
845        | "recover"
846        // Go type constraints
847        | "any"
848        | "comparable"
849        // Special Go identifiers
850        | "init"
851        | "main"
852        // Lisette prelude types and constructors
853        | "Option"
854        | "Result"
855        | "Comparable"
856        | "Ordered"
857        | "Some"
858        | "None"
859        | "Ok"
860        | "Err"
861        // Lisette prelude functions not already covered by Go builtins above
862        | "assert_type"
863        | "imaginary"
864    )
865}