pub mod freeze;
pub mod infer;
pub(crate) mod registration;
pub mod scopes;
pub mod type_env;
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use std::cell::RefCell;
use std::sync::Arc;
use crate::facts::{BindingIdAllocator, Facts};
use crate::store::Store;
use diagnostics::LocalSink;
use ecow::EcoString;
use scopes::Scopes;
use syntax::ast::Visibility as AstVisibility;
use syntax::ast::{Annotation, Expression, Generic, ImportAlias, Span, StructFieldDefinition};
use syntax::program::{Definition, DefinitionBody, File, FileImport, MethodSignatures, Module};
use syntax::types::{SubstitutionMap, Symbol, Type, substitute};
pub use type_env::{EnvResolve, Speculation, TypeEnv, VarState};
#[derive(Debug, Clone)]
pub struct Cursor {
pub module_id: String,
pub file_id: Option<u32>,
}
impl Default for Cursor {
fn default() -> Self {
Self {
module_id: "std".to_string(),
file_id: None,
}
}
}
impl Cursor {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Default)]
pub struct ImportState {
pub imported_modules: HashMap<String, (Vec<StructFieldDefinition>, Type)>,
pub prefix_to_module: HashMap<String, String>,
pub unprefixed_imports: HashSet<String>,
pub failed_imports: HashSet<String>,
}
impl ImportState {
pub fn new() -> Self {
Self::default()
}
pub fn clear(&mut self) {
let prelude = self.imported_modules.remove("prelude");
self.imported_modules.clear();
if let Some(p) = prelude {
self.imported_modules.insert("prelude".to_string(), p);
}
let prelude_mapping = self.prefix_to_module.remove("prelude");
self.prefix_to_module.clear();
if let Some(m) = prelude_mapping {
self.prefix_to_module.insert("prelude".to_string(), m);
}
self.unprefixed_imports.clear();
self.failed_imports.clear();
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum FileContextKind {
Standard,
ImportedTypedef,
Prelude,
}
struct SavedFileContext {
file_id: Option<u32>,
scopes: Scopes,
imports: ImportState,
}
type BuiltinCache = HashMap<String, Type>;
pub struct TaskState<'s> {
pub env: TypeEnv,
pub scopes: Scopes,
pub cursor: Cursor,
pub imports: ImportState,
pub builtins: BuiltinCache,
pub sink: &'s LocalSink,
pub facts: Facts,
pub satisfying_stack: rustc_hash::FxHashSet<(String, String)>,
method_cache: RefCell<HashMap<EcoString, MethodSignatures>>,
pub ufcs_methods: HashSet<(String, String)>,
pub typed_files: Vec<(String, File)>,
pub bound_position_depth: u32,
}
impl<'s> TaskState<'s> {
pub fn new(sink: &'s LocalSink, binding_ids: Arc<BindingIdAllocator>) -> Self {
Self {
env: TypeEnv::new(),
scopes: Scopes::new(),
cursor: Cursor::new(),
imports: ImportState::new(),
builtins: BuiltinCache::default(),
sink,
facts: Facts::new(binding_ids),
satisfying_stack: rustc_hash::FxHashSet::default(),
method_cache: RefCell::new(HashMap::default()),
ufcs_methods: HashSet::default(),
typed_files: Vec::new(),
bound_position_depth: 0,
}
}
pub fn with_fresh_allocator(sink: &'s LocalSink) -> Self {
Self::new(sink, Arc::new(BindingIdAllocator::new()))
}
pub fn new_type_var(&mut self) -> Type {
let id = self.env.fresh(None);
Type::Var { id, hint: None }
}
pub fn new_type_var_with_hint(&mut self, hint: &str) -> Type {
let hint: EcoString = hint.into();
let id = self.env.fresh(Some(hint.clone()));
Type::Var {
id,
hint: Some(hint),
}
}
pub fn type_from_literal_expression(&mut self, expression: &Expression) -> Option<Type> {
use syntax::ast::{Expression, Literal};
match expression {
Expression::Literal { literal, .. } => match literal {
Literal::Integer { .. } => Some(self.type_int()),
Literal::Float { .. } => Some(self.type_float()),
Literal::Boolean(_) => Some(self.type_bool()),
Literal::String { .. } => Some(self.type_string()),
Literal::Char(_) => Some(self.type_char()),
_ => None,
},
Expression::Unary { expression, .. } => self.type_from_literal_expression(expression),
_ => None,
}
}
pub fn instantiate(&mut self, ty: &Type) -> (Type, SubstitutionMap) {
match ty {
Type::Forall { vars, body } => {
let map: SubstitutionMap = vars
.iter()
.map(|name| {
let id = self.env.fresh(Some(name.clone()));
let fresh_var = Type::Var {
id,
hint: Some(name.clone()),
};
(name.clone(), fresh_var)
})
.collect();
(substitute(body, &map), map)
}
_ => (ty.clone(), HashMap::default()),
}
}
pub fn new_file_id(&mut self, store: &Store) -> u32 {
store.new_file_id()
}
pub fn is_d_lis(&self, store: &Store) -> bool {
let Some(file_id) = self.cursor.file_id else {
return false;
};
let Some(module) = store.get_module(&self.cursor.module_id) else {
return false;
};
module.typedefs.contains_key(&file_id)
}
pub fn is_lis(&self, store: &Store) -> bool {
!self.is_d_lis(store)
}
pub(crate) fn current_module<'a>(&self, store: &'a Store) -> &'a Module {
store
.get_module(&self.cursor.module_id)
.expect("current module must exist in store")
}
pub(crate) fn current_module_mut<'a>(&self, store: &'a mut Store) -> &'a mut Module {
store
.get_module_mut(&self.cursor.module_id)
.expect("current module must exist in store")
}
pub(crate) fn qualify_name(&self, name: &str) -> Symbol {
Symbol::from_parts(&self.cursor.module_id, name)
}
pub(crate) fn put_in_scope(&mut self, generics: &[Generic]) {
for (index, generic) in generics.iter().enumerate() {
self.scopes
.current_mut()
.type_params
.get_or_insert_with(HashMap::default)
.insert(generic.name.to_string(), index);
}
}
pub(crate) fn validate_generic_bounds(
&mut self,
store: &Store,
generics: &[Generic],
span: &Span,
) {
for g in generics {
for b in &g.bounds {
self.register_bound_annotation(store, b, span);
}
}
}
pub(crate) fn register_bound_annotation(
&mut self,
store: &Store,
bound: &Annotation,
span: &Span,
) -> Type {
let resolved = self.convert_bound_to_type(store, bound, span);
if self.is_lis(store) && resolved.contains_unknown() {
self.sink
.push(diagnostics::infer::unknown_in_bound_position(
bound.get_span(),
));
}
resolved
}
fn resolve_in_imported_module<'m>(
&self,
module: &'m Module,
simple_name: &str,
) -> Option<(String, &'m Definition)> {
let module_prefix = format!("{}.", module.id);
let direct = format!("{}{}", module_prefix, simple_name);
if let Some(definition) = module.definitions.get(direct.as_str())
&& definition.visibility().is_public()
{
return Some((direct, definition));
}
let suffix = format!(".{}", simple_name);
for (qn, definition) in &module.definitions {
if qn.ends_with(suffix.as_str())
&& qn.starts_with(module_prefix.as_str())
&& definition.visibility().is_public()
{
let rest = &qn[module_prefix.len()..];
if rest.contains('.') {
return Some((qn.to_string(), definition));
}
}
}
None
}
pub(crate) fn lookup_qualified_name(&self, store: &Store, type_name: &str) -> Option<String> {
if let Some((prefix, simple_name)) = type_name.split_once('.')
&& let Some(module_id) = self.imports.prefix_to_module.get(prefix)
&& let Some(imported_module) = store.get_module(module_id)
&& let Some((qualified_name, _)) =
self.resolve_in_imported_module(imported_module, simple_name)
{
return Some(qualified_name);
}
let module = store.get_module(&self.cursor.module_id)?;
let qualified_name = Symbol::from_parts(&module.id, type_name);
if module.definitions.contains_key(qualified_name.as_str()) {
return Some(qualified_name.to_string());
}
for imported_module_id in &self.imports.unprefixed_imports {
if let Some(imported_module) = store.get_module(imported_module_id) {
let qualified_name = Symbol::from_parts(imported_module_id, type_name);
if imported_module
.definitions
.contains_key(qualified_name.as_str())
{
return Some(qualified_name.to_string());
}
}
}
None
}
pub(crate) fn get_definition_name_span(
&self,
store: &Store,
qualified_name: &str,
) -> Option<Span> {
store.get_definition(qualified_name)?.name_span()
}
pub(crate) fn is_const_name(&self, store: &Store, qualified_name: &str) -> bool {
if qualified_name.starts_with("go:") {
return false;
}
store
.module_for_qualified_name(qualified_name)
.and_then(|module_id| store.get_module(module_id))
.is_some_and(|module| module.const_names.contains(qualified_name))
}
pub(crate) fn is_const_var(&self, store: &Store, var_name: &str) -> bool {
if self.scopes.lookup_binding_id(var_name).is_some() {
return false;
}
if self.scopes.lookup_const(var_name) {
return true;
}
self.lookup_qualified_name(store, var_name)
.is_some_and(|qname| self.is_const_name(store, &qname))
}
pub(crate) fn track_name_usage(
&mut self,
store: &Store,
qualified_name: &str,
span: &Span,
name_len: u32,
) {
if let Some(definition_span) = self.get_definition_name_span(store, qualified_name) {
let usage_span = Span::new(span.file_id, span.byte_offset, name_len);
self.facts.add_usage(usage_span, definition_span);
}
}
pub(crate) fn lookup_generic_index(&self, type_name: &str) -> Option<usize> {
self.scopes.lookup_type_param(type_name)
}
fn resolve_definition_value_type(&self, store: &Store, definition: &Definition) -> Type {
if let DefinitionBody::Struct {
constructor: Some(ctor_ty),
..
} = &definition.body
{
return ctor_ty.clone();
}
if let DefinitionBody::TypeAlias { .. } = &definition.body {
let alias_ty = &definition.ty;
let underlying = match alias_ty {
Type::Forall { body, .. } => body.as_ref(),
other => other,
};
if let Type::Nominal { id, .. } = underlying
&& let Some(Definition {
body:
DefinitionBody::Struct {
constructor: Some(ctor_ty),
..
},
..
}) = store.get_definition(id)
{
return ctor_ty.clone();
}
}
definition.ty().clone()
}
pub(crate) fn lookup_type(&self, store: &Store, value_name: &str) -> Option<Type> {
if let Some(ty) = self.scopes.lookup_value(value_name) {
return Some(ty.clone());
}
if let Some((_definition, ty)) = self.imports.imported_modules.get(value_name) {
return Some(ty.clone());
}
if let Some((prefix, rest)) = value_name.split_once('.')
&& let Some(module_id) = self.imports.prefix_to_module.get(prefix)
&& let Some(imported_module) = store.get_module(module_id)
&& let Some((_, definition)) = self.resolve_in_imported_module(imported_module, rest)
{
return Some(self.resolve_definition_value_type(store, definition));
}
let module = store.get_module(&self.cursor.module_id)?;
let qualified_name = Symbol::from_parts(&module.id, value_name);
if let Some(definition) = module.definitions.get(qualified_name.as_str()) {
return Some(self.resolve_definition_value_type(store, definition));
}
for imported_module_id in &self.imports.unprefixed_imports {
if let Some(imported_module) = store.get_module(imported_module_id) {
let qualified_name = Symbol::from_parts(imported_module_id, value_name);
if let Some(definition) = imported_module.definitions.get(qualified_name.as_str()) {
return Some(self.resolve_definition_value_type(store, definition));
}
}
}
None
}
pub(crate) fn is_enum_type(&self, store: &Store, ty: &Type) -> bool {
let Type::Nominal { id, .. } = ty else {
return false;
};
let Some(definition) = store.get_definition(id) else {
return false;
};
matches!(
definition.body,
DefinitionBody::Enum { .. } | DefinitionBody::ValueEnum { .. }
)
}
pub(crate) fn resolve_type_name(
&mut self,
store: &Store,
type_name: &str,
) -> Option<(String, Type)> {
if self.scopes.lookup_type_param(type_name).is_some() {
return None;
}
let qualified_name = self.lookup_qualified_name(store, type_name)?;
let ty = store.get_type(&qualified_name)?.clone();
Some((qualified_name, ty))
}
pub(crate) fn resolve_type_from_prelude(
&self,
store: &Store,
type_name: &str,
) -> Option<(String, Type)> {
let qualified_name = format!("prelude.{}", type_name);
let ty = store.get_type(&qualified_name)?.clone();
Some((qualified_name, ty))
}
pub(crate) fn get_all_methods(&self, store: &Store, ty: &Type) -> MethodSignatures {
if let Type::Parameter(name) = ty {
let trait_bounds = self.scopes.collect_all_trait_bounds();
let qualified_name = self.qualify_name(name);
return store.get_methods_from_bounds(&qualified_name, &trait_bounds);
}
let resolved = ty.strip_refs().resolve_in(&self.env);
let cache_key: EcoString = match &resolved {
Type::Nominal { id, .. } => id.as_eco().clone(),
Type::Compound { kind, .. } => format!("prelude.{}", kind.leaf_name()).into(),
Type::Simple(kind) => format!("prelude.{}", kind.leaf_name()).into(),
_ => return MethodSignatures::default(),
};
let peeled = store.peel_alias(&resolved);
if let Type::Nominal { id: peeled_id, .. } = &peeled
&& store.get_interface(peeled_id).is_some()
{
let empty = HashMap::default();
return store.get_all_methods(&peeled, &empty);
}
if let Some(cached) = self.method_cache.borrow().get(cache_key.as_str()) {
return cached.clone();
}
let empty = HashMap::default();
let methods = store.get_all_methods(&resolved, &empty);
self.method_cache
.borrow_mut()
.insert(cache_key, methods.clone());
methods
}
pub fn reset_scopes(&mut self) {
self.scopes.reset();
self.imports.clear();
}
pub(crate) fn with_module_cursor<T>(
&mut self,
module_id: &str,
f: impl FnOnce(&mut Self) -> T,
) -> T {
if self.cursor.module_id == module_id {
return f(self);
}
let previous_module_id = std::mem::replace(&mut self.cursor.module_id, module_id.into());
let result = f(self);
self.cursor.module_id = previous_module_id;
result
}
pub(crate) fn with_file_context<T>(
&mut self,
store: &Store,
module_id: &str,
file_id: u32,
imports: &[FileImport],
kind: FileContextKind,
f: impl FnOnce(&mut Self, &Store) -> T,
) -> T {
self.with_module_cursor(module_id, |this| {
let saved = this.enter_file_context(store, module_id, file_id, imports, kind);
let result = f(this, store);
this.exit_file_context(saved);
result
})
}
pub(crate) fn with_file_context_mut<T>(
&mut self,
store: &mut Store,
module_id: &str,
file_id: u32,
imports: &[FileImport],
kind: FileContextKind,
f: impl FnOnce(&mut Self, &mut Store) -> T,
) -> T {
self.with_module_cursor(module_id, |this| {
let saved = this.enter_file_context(&*store, module_id, file_id, imports, kind);
let result = f(this, store);
this.exit_file_context(saved);
result
})
}
fn enter_file_context(
&mut self,
store: &Store,
module_id: &str,
file_id: u32,
imports: &[FileImport],
kind: FileContextKind,
) -> SavedFileContext {
let saved = SavedFileContext {
file_id: self.cursor.file_id.replace(file_id),
scopes: std::mem::take(&mut self.scopes),
imports: std::mem::take(&mut self.imports),
};
match kind {
FileContextKind::Standard => {
self.put_prelude_in_scope(store);
self.put_unprefixed_module_in_scope(store, module_id);
}
FileContextKind::ImportedTypedef => {
self.put_prelude_in_scope(store);
}
FileContextKind::Prelude => {
self.put_unprefixed_module_in_scope(store, module_id);
}
}
self.put_imported_modules_in_scope(store, imports);
saved
}
fn exit_file_context(&mut self, saved: SavedFileContext) {
self.scopes = saved.scopes;
self.imports = saved.imports;
self.cursor.file_id = saved.file_id;
}
pub fn failed(&self) -> bool {
self.sink.has_errors()
}
pub fn put_prelude_in_scope(&mut self, store: &Store) {
self.put_unprefixed_module_in_scope(store, "prelude");
if self.imports.imported_modules.contains_key("prelude") {
return;
}
self.put_module_in_scope(store, "prelude", Some("prelude".to_string()));
}
pub fn put_unprefixed_module_in_scope(&mut self, store: &Store, module_id: &str) {
self.put_module_in_scope(store, module_id, None)
}
pub fn put_imported_modules_in_scope(&mut self, store: &Store, imports: &[FileImport]) {
let mut seen_aliases: HashMap<String, String> = HashMap::default(); let mut seen_paths: HashSet<String> = HashSet::default();
for import in imports {
if seen_paths.contains(import.name.as_str()) {
self.sink.push(diagnostics::infer::duplicate_import_path(
&import.name,
import.name_span,
));
continue;
}
seen_paths.insert(import.name.to_string());
if matches!(import.alias, Some(ImportAlias::Blank(_))) {
continue;
}
if let Some(ImportAlias::Named(alias, alias_span)) = &import.alias
&& is_reserved_import_alias(alias)
{
self.sink.push(diagnostics::infer::reserved_import_alias(
alias,
*alias_span,
));
continue;
}
let Some(effective) = import.effective_alias(&store.go_package_names) else {
continue;
};
if let Some(existing_path) = seen_aliases.get(&effective)
&& existing_path != &import.name
{
self.sink.push(diagnostics::infer::import_conflict(
&effective,
existing_path,
&import.name,
import.name_span,
));
continue;
}
seen_aliases.insert(effective.clone(), import.name.to_string());
let module = store.get_module(&import.name);
if module.is_none() || module.is_some_and(Module::is_empty_stub) {
self.imports.failed_imports.insert(effective);
continue;
}
self.put_module_in_scope(store, &import.name, Some(effective));
}
}
pub fn put_module_in_scope(&mut self, store: &Store, module_id: &str, prefix: Option<String>) {
let Some(prefix) = prefix else {
self.imports
.unprefixed_imports
.insert(module_id.to_string());
return;
};
let module = store
.get_module(module_id)
.expect("module must exist when putting in scope");
let imported_module_id = module.id.clone();
let module_prefix = format!("{}.", module.id);
let module_struct_fields: Vec<_> = module
.definitions
.iter()
.filter(|(qn, _)| module.is_public(qn))
.filter(|(qn, _)| {
qn.strip_prefix(&module_prefix)
.is_some_and(|rest| !rest.contains('.'))
})
.map(|(qn, definition)| {
let simple_name = qn
.strip_prefix(&module_prefix)
.expect("qualified_name must start with module prefix");
let ty = if let DefinitionBody::Struct {
constructor: Some(ctor_ty),
..
} = &definition.body
{
ctor_ty.clone()
} else {
definition.ty().clone()
};
StructFieldDefinition {
doc: None,
attributes: vec![],
visibility: AstVisibility::Public,
name: simple_name.into(),
name_span: Span::dummy(),
annotation: Annotation::Unknown,
ty,
}
})
.collect();
let ty = Type::ImportNamespace(imported_module_id.clone().into());
self.imports
.imported_modules
.insert(prefix.clone(), (module_struct_fields, ty));
self.imports
.prefix_to_module
.insert(prefix, imported_module_id);
}
pub(crate) fn speculatively<T, E>(
&mut self,
f: impl FnOnce(&mut Self) -> Result<T, E>,
) -> Result<T, E> {
let spec = self.env.begin_speculation();
let result = f(self);
self.env.end_speculation(spec, result.is_err());
result
}
}
fn is_reserved_import_alias(name: &str) -> bool {
matches!(
name,
"break"
| "case"
| "chan"
| "const"
| "continue"
| "default"
| "defer"
| "else"
| "fallthrough"
| "for"
| "func"
| "go"
| "goto"
| "if"
| "interface"
| "map"
| "package"
| "range"
| "return"
| "select"
| "struct"
| "switch"
| "type"
| "var"
| "nil"
| "iota"
| "true"
| "false"
| "bool"
| "byte"
| "complex64"
| "complex128"
| "error"
| "float32"
| "float64"
| "int"
| "int8"
| "int16"
| "int32"
| "int64"
| "rune"
| "string"
| "uint"
| "uint8"
| "uint16"
| "uint32"
| "uint64"
| "uintptr"
| "append"
| "cap"
| "clear"
| "close"
| "complex"
| "copy"
| "delete"
| "imag"
| "len"
| "make"
| "max"
| "min"
| "new"
| "panic"
| "print"
| "println"
| "real"
| "recover"
| "any"
| "comparable"
| "init"
| "main"
| "Option"
| "Result"
| "Comparable"
| "Ordered"
| "Some"
| "None"
| "Ok"
| "Err"
| "assert_type"
| "imaginary"
)
}