diff --git a/crates/cli/src/error.rs b/crates/cli/src/error.rs index 77585484..24444774 100644 --- a/crates/cli/src/error.rs +++ b/crates/cli/src/error.rs @@ -45,6 +45,15 @@ pub enum Error { }, } +impl Error { + pub fn total(&self) -> usize { + match self { + Error::List(errors) => errors.len(), + _ => 1, + } + } +} + impl Debug for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let miette_handler = MietteHandlerOpts::new() diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index a848dc35..e639f9a6 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -49,15 +49,23 @@ fn main() -> miette::Result<()> { let mut project = Project::new(config, project_path); - if let Err(err) = project.build() { - match err { + let build_result = project.build(); + + for warning in project.warnings { + eprintln!("Warning: {:?}", warning) + } + + if let Err(err) = build_result { + match &err { error::Error::List(errors) => { for error in errors { eprintln!("Error: {:?}", error) } } - rest => Err(rest)?, + rest => eprintln!("Error: {:?}", rest), } + + miette::bail!("failed: {} errors", err.total()); }; } diff --git a/crates/cli/src/project.rs b/crates/cli/src/project.rs index badb0cbe..7d311904 100644 --- a/crates/cli/src/project.rs +++ b/crates/cli/src/project.rs @@ -42,7 +42,7 @@ pub struct Project { module_types: HashMap, root: PathBuf, sources: Vec, - warnings: Vec, + pub warnings: Vec, } impl Project { diff --git a/crates/lang/src/ast.rs b/crates/lang/src/ast.rs index 64d6cf50..2c417503 100644 --- a/crates/lang/src/ast.rs +++ b/crates/lang/src/ast.rs @@ -181,6 +181,38 @@ pub struct FieldMap { pub fields: HashMap, } +impl FieldMap { + pub fn new(arity: usize) -> Self { + Self { + arity, + fields: HashMap::new(), + } + } + + pub fn insert( + &mut self, + label: String, + index: usize, + location: &Span, + ) -> Result<(), tipo::error::Error> { + match self.fields.insert(label.clone(), index) { + Some(_) => Err(tipo::error::Error::DuplicateField { + label, + location: *location, + }), + None => Ok(()), + } + } + + pub fn into_option(self) -> Option { + if self.fields.is_empty() { + None + } else { + Some(self) + } + } +} + #[derive(Debug, Clone, PartialEq)] pub struct RecordConstructor { pub location: Span, @@ -261,17 +293,84 @@ pub enum Annotation { name: String, }, - Tuple { - location: Span, - elems: Vec, - }, - Hole { location: Span, name: String, }, } +impl Annotation { + pub fn location(&self) -> Span { + match self { + Annotation::Fn { location, .. } + | Annotation::Var { location, .. } + | Annotation::Hole { location, .. } + | Annotation::Constructor { location, .. } => *location, + } + } + + pub fn is_logically_equal(&self, other: &Annotation) -> bool { + match self { + Annotation::Constructor { + module, + name, + arguments, + location: _, + } => match other { + Annotation::Constructor { + module: o_module, + name: o_name, + arguments: o_arguments, + location: _, + } => { + module == o_module + && name == o_name + && arguments.len() == o_arguments.len() + && arguments + .iter() + .zip(o_arguments) + .all(|a| a.0.is_logically_equal(a.1)) + } + _ => false, + }, + Annotation::Fn { + arguments, + ret, + location: _, + } => match other { + Annotation::Fn { + arguments: o_arguments, + ret: o_return, + location: _, + } => { + arguments.len() == o_arguments.len() + && arguments + .iter() + .zip(o_arguments) + .all(|a| a.0.is_logically_equal(a.1)) + && ret.is_logically_equal(o_return) + } + _ => false, + }, + Annotation::Var { name, location: _ } => match other { + Annotation::Var { + name: o_name, + location: _, + } => name == o_name, + _ => false, + }, + + Annotation::Hole { name, location: _ } => match other { + Annotation::Hole { + name: o_name, + location: _, + } => name == o_name, + _ => false, + }, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum Layer { Value, @@ -284,7 +383,7 @@ impl Default for Layer { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BinOp { // Boolean logic And, diff --git a/crates/lang/src/builtins.rs b/crates/lang/src/builtins.rs index 3e73eae4..864766cc 100644 --- a/crates/lang/src/builtins.rs +++ b/crates/lang/src/builtins.rs @@ -34,7 +34,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module { parameters: vec![], tipo: int(), origin: Span::empty(), - module: vec![], + module: "".to_string(), public: true, }, ); @@ -46,7 +46,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module { origin: Span::empty(), parameters: vec![], tipo: byte_array(), - module: vec![], + module: "".to_string(), public: true, }, ); @@ -93,7 +93,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module { origin: Span::empty(), parameters: vec![], tipo: bool(), - module: vec![], + module: "".to_string(), public: true, }, ); @@ -106,7 +106,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module { origin: Span::empty(), parameters: vec![list_parameter.clone()], tipo: list(list_parameter), - module: vec![], + module: "".to_string(), public: true, }, ); @@ -118,7 +118,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module { origin: Span::empty(), parameters: vec![], tipo: string(), - module: vec![], + module: "".to_string(), public: true, }, ); @@ -145,7 +145,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module { origin: Span::empty(), parameters: vec![], tipo: nil(), - module: vec![], + module: "".to_string(), public: true, }, ); @@ -160,7 +160,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module { origin: Span::empty(), parameters: vec![result_value.clone(), result_error.clone()], tipo: result(result_value, result_error), - module: vec![], + module: "".to_string(), public: true, }, ); @@ -211,7 +211,7 @@ pub fn int() -> Arc { Arc::new(Type::App { public: true, name: INT.to_string(), - module: vec![], + module: "".to_string(), args: vec![], }) } @@ -221,7 +221,7 @@ pub fn byte_array() -> Arc { args: vec![], public: true, name: BYTE_ARRAY.to_string(), - module: vec![], + module: "".to_string(), }) } @@ -230,7 +230,7 @@ pub fn bool() -> Arc { args: vec![], public: true, name: BOOL.to_string(), - module: vec![], + module: "".to_string(), }) } @@ -238,7 +238,7 @@ pub fn list(t: Arc) -> Arc { Arc::new(Type::App { public: true, name: LIST.to_string(), - module: vec![], + module: "".to_string(), args: vec![t], }) } @@ -248,7 +248,7 @@ pub fn string() -> Arc { args: vec![], public: true, name: STRING.to_string(), - module: vec![], + module: "".to_string(), }) } @@ -257,7 +257,7 @@ pub fn nil() -> Arc { args: vec![], public: true, name: NIL.to_string(), - module: vec![], + module: "".to_string(), }) } @@ -265,7 +265,7 @@ pub fn result(a: Arc, e: Arc) -> Arc { Arc::new(Type::App { public: true, name: RESULT.to_string(), - module: vec![], + module: "".to_string(), args: vec![a, e], }) } @@ -279,3 +279,9 @@ pub fn generic_var(id: u64) -> Arc { Arc::new(Type::Var { tipo }) } + +pub fn unbound_var(id: u64) -> Arc { + let tipo = Arc::new(RefCell::new(TypeVar::Unbound { id })); + + Arc::new(Type::Var { tipo }) +} diff --git a/crates/lang/src/lib.rs b/crates/lang/src/lib.rs index 0d36e5ca..460229ab 100644 --- a/crates/lang/src/lib.rs +++ b/crates/lang/src/lib.rs @@ -1,4 +1,7 @@ -use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, +}; pub mod ast; pub mod builtins; @@ -9,9 +12,9 @@ pub mod parser; pub mod tipo; pub mod token; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct IdGenerator { - id: AtomicU64, + id: Arc, } impl IdGenerator { diff --git a/crates/lang/src/tipo.rs b/crates/lang/src/tipo.rs index 078ede63..1024ef00 100644 --- a/crates/lang/src/tipo.rs +++ b/crates/lang/src/tipo.rs @@ -2,7 +2,9 @@ use std::{cell::RefCell, collections::HashMap, sync::Arc}; use crate::ast::{Constant, FieldMap, ModuleKind, Span, TypedConstant}; +mod environment; pub mod error; +mod hydrator; pub mod infer; #[derive(Debug, Clone, PartialEq)] @@ -17,7 +19,7 @@ pub enum Type { /// App { public: bool, - module: Vec, + module: String, name: String, args: Vec>, }, @@ -32,12 +34,11 @@ pub enum Type { /// A type variable. See the contained `TypeVar` enum for more information. /// Var { tipo: Arc> }, - - /// A tuple is an ordered collection of 0 or more values, each of which - /// can have a different type, so the `tuple` type is the sum of all the - /// contained types. - /// - Tuple { elems: Vec> }, + // /// A tuple is an ordered collection of 0 or more values, each of which + // /// can have a different type, so the `tuple` type is the sum of all the + // /// contained types. + // /// + // Tuple { elems: Vec> }, } #[derive(Debug, Clone, PartialEq)] @@ -67,6 +68,12 @@ pub enum TypeVar { Generic { id: u64 }, } +impl TypeVar { + pub fn is_unbound(&self) -> bool { + matches!(self, Self::Unbound { .. }) + } +} + #[derive(Debug, Clone, PartialEq)] pub struct ValueConstructor { pub public: bool, @@ -100,7 +107,7 @@ pub enum ValueConstructorVariant { ModuleFn { name: String, field_map: Option, - module: Vec, + module: String, arity: usize, location: Span, }, @@ -131,7 +138,7 @@ pub struct Module { pub struct TypeConstructor { pub public: bool, pub origin: Span, - pub module: Vec, + pub module: String, pub parameters: Vec>, pub tipo: Arc, } diff --git a/crates/lang/src/tipo/environment.rs b/crates/lang/src/tipo/environment.rs new file mode 100644 index 00000000..040c5859 --- /dev/null +++ b/crates/lang/src/tipo/environment.rs @@ -0,0 +1,1161 @@ +use std::{ + collections::{HashMap, HashSet}, + ops::Deref, + sync::Arc, +}; + +use crate::{ + ast::{ + Annotation, ArgName, Definition, FieldMap, RecordConstructor, RecordConstructorArg, Span, + UnqualifiedImport, UntypedDefinition, + }, + builtins::{function, generic_var, unbound_var}, + IdGenerator, +}; + +use super::{ + error::{Error, Warning}, + hydrator::Hydrator, + AccessorsMap, Module, RecordAccessor, Type, TypeConstructor, TypeVar, ValueConstructor, + ValueConstructorVariant, +}; + +#[derive(Debug)] +pub struct Environment<'a> { + /// Accessors defined in the current module + pub accessors: HashMap, + pub current_module: &'a String, + /// entity_usages is a stack of scopes. When an entity is created it is + /// added to the top scope. When an entity is used we crawl down the scope + /// stack for an entity with that name and mark it as used. + /// NOTE: The bool in the tuple here tracks if the entity has been used + pub entity_usages: Vec>, + pub id_gen: IdGenerator, + pub importable_modules: &'a HashMap, + + /// Modules that have been imported by the current module, along with the + /// location of the import statement where they were imported. + pub imported_modules: HashMap, + pub imported_types: HashSet, + + /// Types defined in the current module (or the prelude) + pub module_types: HashMap, + + /// Mapping from types to constructor names in the current module (or the prelude) + pub module_types_constructors: HashMap>, + + /// Values defined in the current module (or the prelude) + pub module_values: HashMap, + + previous_id: u64, + + /// Values defined in the current function (or the prelude) + pub scope: HashMap, + + /// Functions that have not yet been inferred then generalised. + /// We use this to determine whether functions that call this one + /// can safely be generalised. + pub ungeneralised_functions: HashSet, + + /// Names of types or values that have been imported an unqualified fashion + /// from other modules. Used to prevent multiple imports using the same name. + pub unqualified_imported_names: HashMap, + + pub unused_modules: HashMap, + + /// Warnings + pub warnings: &'a mut Vec, +} + +impl<'a> Environment<'a> { + pub fn new( + id_gen: IdGenerator, + current_module: &'a String, + importable_modules: &'a HashMap, + warnings: &'a mut Vec, + ) -> Self { + let prelude = importable_modules + .get("aiken") + .expect("Unable to find prelude in importable modules"); + + Self { + previous_id: id_gen.next(), + id_gen, + ungeneralised_functions: HashSet::new(), + module_types: prelude.types.clone(), + module_types_constructors: prelude.types_constructors.clone(), + module_values: HashMap::new(), + imported_modules: HashMap::new(), + unused_modules: HashMap::new(), + unqualified_imported_names: HashMap::new(), + accessors: prelude.accessors.clone(), + scope: prelude.values.clone(), + importable_modules, + imported_types: HashSet::new(), + current_module, + warnings, + entity_usages: vec![HashMap::new()], + } + } + + pub fn register_import(&mut self, def: &UntypedDefinition) -> Result<(), Error> { + match def { + Definition::Use { + module, + as_name, + unqualified, + location, + .. + } => { + let name = module.join("/"); + + // Find imported module + let module_info = + self.importable_modules + .get(&name) + .ok_or_else(|| Error::UnknownModule { + location: *location, + name, + imported_modules: self.imported_modules.keys().cloned().collect(), + })?; + + // Determine local alias of imported module + let module_name = as_name + .as_ref() + .or_else(|| module.last()) + .expect("Typer could not identify module name.") + .clone(); + + // Insert unqualified imports into scope + for UnqualifiedImport { + name, + location, + as_name, + .. + } in unqualified + { + let mut type_imported = false; + let mut value_imported = false; + let mut variant = None; + + let imported_name = as_name.as_ref().unwrap_or(name); + + // Check if value already was imported + if let Some(previous) = self.unqualified_imported_names.get(imported_name) { + return Err(Error::DuplicateImport { + location: *location, + previous_location: *previous, + name: name.to_string(), + }); + } + + // Register the name as imported so it can't be imported a + // second time in future + self.unqualified_imported_names + .insert(imported_name.clone(), *location); + + // Register the unqualified import if it is a value + if let Some(value) = module_info.values.get(name) { + self.insert_variable( + imported_name.clone(), + value.variant.clone(), + value.tipo.clone(), + ); + variant = Some(&value.variant); + value_imported = true; + } + + // Register the unqualified import if it is a type constructor + if let Some(typ) = module_info.types.get(name) { + let typ_info = TypeConstructor { + origin: *location, + ..typ.clone() + }; + + self.insert_type_constructor(imported_name.clone(), typ_info)?; + + type_imported = true; + } + + if value_imported && type_imported { + self.init_usage( + imported_name.to_string(), + EntityKind::ImportedTypeAndConstructor, + *location, + ); + } else if type_imported { + self.imported_types.insert(imported_name.to_string()); + + self.init_usage( + imported_name.to_string(), + EntityKind::ImportedType, + *location, + ); + } else if value_imported { + match variant { + Some(&ValueConstructorVariant::Record { .. }) => self.init_usage( + imported_name.to_string(), + EntityKind::ImportedConstructor, + *location, + ), + _ => self.init_usage( + imported_name.to_string(), + EntityKind::ImportedValue, + *location, + ), + }; + } else if !value_imported { + // Error if no type or value was found with that name + return Err(Error::UnknownModuleField { + location: *location, + name: name.clone(), + module_name: module.join("/"), + value_constructors: module_info + .values + .keys() + .map(|t| t.to_string()) + .collect(), + type_constructors: module_info + .types + .keys() + .map(|t| t.to_string()) + .collect(), + }); + } + } + + if unqualified.is_empty() { + // When the module has no unqualified imports, we track its usage + // so we can warn if not used by the end of the type checking + self.unused_modules.insert(module_name.clone(), *location); + } + + // Check if a module was already imported with this name + if let Some((previous_location, _)) = self.imported_modules.get(&module_name) { + return Err(Error::DuplicateImport { + location: *location, + previous_location: *previous_location, + name: module_name, + }); + } + + // Register the name as imported so it can't be imported a + // second time in future + self.unqualified_imported_names + .insert(module_name.clone(), *location); + + // Insert imported module into scope + self.imported_modules + .insert(module_name, (*location, module_info)); + + Ok(()) + } + + _ => Ok(()), + } + } + + /// Iterate over a module, registering any new types created by the module into the typer + pub fn register_types( + &mut self, + def: &'a UntypedDefinition, + module: &String, + hydrators: &mut HashMap, + names: &mut HashMap<&'a str, &'a Span>, + ) -> Result<(), Error> { + match def { + Definition::DataType { + name, + public, + parameters, + location, + constructors, + .. + } => { + assert_unique_type_name(names, name, location)?; + + // Build a type from the type Annotation + let mut hydrator = Hydrator::new(); + + let parameters = self.make_type_vars(parameters, location, &mut hydrator)?; + + let tipo = Arc::new(Type::App { + public: *public, + module: module.to_owned(), + name: name.clone(), + args: parameters.clone(), + }); + + hydrators.insert(name.to_string(), hydrator); + + self.insert_type_constructor( + name.clone(), + TypeConstructor { + origin: *location, + module: module.to_owned(), + public: *public, + parameters, + tipo, + }, + )?; + + let constructor_names = constructors.iter().map(|c| c.name.clone()).collect(); + + self.insert_type_to_constructors(name.clone(), constructor_names); + + // Keep track of private types so we can tell if they are later unused + if !public { + self.init_usage(name.clone(), EntityKind::PrivateType, *location); + } + } + + Definition::TypeAlias { + location, + public, + parameters: args, + alias: name, + annotation: resolved_type, + .. + } => { + assert_unique_type_name(names, name, location)?; + + // Register the paramerterised types + let mut hydrator = Hydrator::new(); + let parameters = self.make_type_vars(args, location, &mut hydrator)?; + + // Disallow creation of new types outside the paramerterised types + hydrator.disallow_new_type_variables(); + + // Create the type that the alias resolves to + let tipo = hydrator.type_from_annotation(resolved_type, self)?; + + self.insert_type_constructor( + name.clone(), + TypeConstructor { + origin: *location, + module: module.to_owned(), + public: *public, + parameters, + tipo, + }, + )?; + + // Keep track of private types so we can tell if they are later unused + if !public { + self.init_usage(name.clone(), EntityKind::PrivateType, *location); + } + } + + Definition::Fn { .. } | Definition::Use { .. } | Definition::ModuleConstant { .. } => {} + } + + Ok(()) + } + + pub fn register_values( + &mut self, + def: &'a UntypedDefinition, + module_name: &String, + hydrators: &mut HashMap, + names: &mut HashMap<&'a str, &'a Span>, + ) -> Result<(), Error> { + match def { + Definition::Fn { + name, + arguments: args, + location, + return_annotation, + public, + .. + } => { + assert_unique_value_name(names, name, location)?; + + self.ungeneralised_functions.insert(name.to_string()); + + // Create the field map so we can reorder labels for usage of this function + let mut field_map = FieldMap::new(args.len()); + + for (i, arg) in args.iter().enumerate() { + if let ArgName::NamedLabeled { label, .. } + | ArgName::LabeledDiscard { label, .. } = &arg.arg_name + { + field_map.insert(label.clone(), i, location)?; + } + } + let field_map = field_map.into_option(); + + // Construct type from annotations + let mut hydrator = Hydrator::new(); + + hydrator.permit_holes(true); + + let mut arg_types = Vec::new(); + + for arg in args { + let tipo = hydrator.type_from_option_annotation(&arg.annotation, self)?; + + arg_types.push(tipo); + } + + let return_type = hydrator.type_from_option_annotation(return_annotation, self)?; + + let tipo = function(arg_types, return_type); + + // Keep track of which types we create from annotations so we can know + // which generic types not to instantiate later when performing + // inference of the function body. + hydrators.insert(name.clone(), hydrator); + + // Insert the function into the environment + self.insert_variable( + name.clone(), + ValueConstructorVariant::ModuleFn { + name: name.clone(), + field_map, + module: module_name.to_owned(), + arity: args.len(), + location: *location, + }, + tipo, + ); + + if !public { + self.init_usage(name.clone(), EntityKind::PrivateFunction, *location); + } + } + + Definition::DataType { + location, + public, + opaque, + name, + constructors, + .. + } => { + let mut hydrator = hydrators + .remove(name) + .expect("Could not find hydrator for register_values custom type"); + + hydrator.disallow_new_type_variables(); + + let typ = self + .module_types + .get(name) + .expect("Type for custom type not found in register_values") + .tipo + .clone(); + + // If the custom type only has a single constructor then we can access the + // fields using the record.field syntax, so store any fields accessors. + if let Some(accessors) = self.custom_type_accessors(constructors, &mut hydrator)? { + let map = AccessorsMap { + public: (*public && !*opaque), + accessors, + // TODO: improve the ownership here so that we can use the + // `return_type_constructor` below rather than looking it up twice. + tipo: typ.clone(), + }; + + self.insert_accessors(name, map) + } + + // Check and register constructors + for constructor in constructors { + assert_unique_value_name(names, &constructor.name, &constructor.location)?; + + let mut field_map = FieldMap::new(constructor.arguments.len()); + + let mut args_types = Vec::with_capacity(constructor.arguments.len()); + + for ( + i, + RecordConstructorArg { + label, annotation, .. + }, + ) in constructor.arguments.iter().enumerate() + { + let t = hydrator.type_from_annotation(annotation, self)?; + + args_types.push(t); + + if let Some(label) = label { + field_map.insert(label.clone(), i, location)?; + } + } + + let field_map = field_map.into_option(); + + // Insert constructor function into module scope + let typ = match constructor.arguments.len() { + 0 => typ.clone(), + _ => function(args_types, typ.clone()), + }; + + let constructor_info = ValueConstructorVariant::Record { + constructors_count: constructors.len() as u16, + name: constructor.name.clone(), + arity: constructor.arguments.len(), + field_map: field_map.clone(), + location: constructor.location, + module: module_name.to_owned(), + }; + + if !opaque { + self.insert_module_value( + &constructor.name, + ValueConstructor { + public: *public, + tipo: typ.clone(), + variant: constructor_info.clone(), + }, + ); + } + + if !public { + self.init_usage( + constructor.name.clone(), + EntityKind::PrivateTypeConstructor(name.clone()), + constructor.location, + ); + } + + self.insert_variable(constructor.name.clone(), constructor_info, typ); + } + } + + Definition::ModuleConstant { name, location, .. } => { + assert_unique_const_name(names, name, location)?; + } + + Definition::Use { .. } | Definition::TypeAlias { .. } => {} + } + Ok(()) + } + + /// Insert a variable in the current scope. + pub fn insert_variable( + &mut self, + name: String, + variant: ValueConstructorVariant, + tipo: Arc, + ) { + self.scope.insert( + name, + ValueConstructor { + public: false, + variant, + tipo, + }, + ); + } + + /// Map a type in the current scope. Errors if the module + /// already has a type with that name, unless the type is + /// from the prelude. + pub fn insert_type_constructor( + &mut self, + type_name: String, + info: TypeConstructor, + ) -> Result<(), Error> { + let name = type_name.clone(); + let location = info.origin; + + match self.module_types.insert(type_name, info) { + None => Ok(()), + Some(prelude_type) if prelude_type.module.is_empty() => Ok(()), + Some(previous) => Err(Error::DuplicateTypeName { + name, + location, + previous_location: previous.origin, + }), + } + } + + /// Map a type to constructors in the current scope. + pub fn insert_type_to_constructors(&mut self, type_name: String, constructors: Vec) { + self.module_types_constructors + .insert(type_name, constructors); + } + + pub fn insert_accessors(&mut self, type_name: &str, accessors: AccessorsMap) { + self.accessors.insert(type_name.to_string(), accessors); + } + + /// Insert a value into the current module. + /// Errors if the module already has a value with that name. + pub fn insert_module_value(&mut self, name: &str, value: ValueConstructor) { + self.module_values.insert(name.to_string(), value); + } + + /// Lookup a type in the current scope. + pub fn get_type_constructor( + &mut self, + module_alias: &Option, + name: &str, + location: Span, + ) -> Result<&TypeConstructor, Error> { + match module_alias { + None => self + .module_types + .get(name) + .ok_or_else(|| Error::UnknownTypeConstructorType { + location, + name: name.to_string(), + type_constructors: self.module_types.keys().map(|t| t.to_string()).collect(), + }), + + Some(m) => { + let (_, module) = self.imported_modules.get(m).ok_or_else(|| { + Error::UnknownTypeConstructorModule { + location, + name: name.to_string(), + imported_modules: self + .importable_modules + .keys() + .map(|t| t.to_string()) + .collect(), + } + })?; + self.unused_modules.remove(m); + module + .types + .get(name) + .ok_or_else(|| Error::UnknownTypeConstructorModuleType { + location, + name: name.to_string(), + module_name: module.name.clone(), + type_constructors: module.types.keys().map(|t| t.to_string()).collect(), + }) + } + } + } + + /// Increments an entity's usage in the current or nearest enclosing scope + pub fn increment_usage(&mut self, name: &str) { + let mut name = name.to_string(); + + while let Some((kind, _, used)) = self + .entity_usages + .iter_mut() + .rev() + .find_map(|scope| scope.get_mut(&name)) + { + *used = true; + + match kind { + // If a type constructor is used, we consider its type also used + EntityKind::PrivateTypeConstructor(type_name) if type_name != &name => { + name.clone_from(type_name); + } + _ => return, + } + } + } + + /// Inserts an entity at the current scope for usage tracking. + pub fn init_usage(&mut self, name: String, kind: EntityKind, location: Span) { + use EntityKind::*; + + match self + .entity_usages + .last_mut() + .expect("Attempted to access non-existant entity usages scope") + .insert(name.to_string(), (kind, location, false)) + { + // Private types can be shadowed by a constructor with the same name + // + // TODO: Improve this so that we can tell if an imported overriden + // type is actually used or not by tracking whether usages apply to + // the value or type scope + Some((ImportedType | ImportedTypeAndConstructor | PrivateType, _, _)) => (), + + Some((kind, location, false)) => { + // an entity was overwritten in the top most scope without being used + let mut unused = HashMap::with_capacity(1); + unused.insert(name, (kind, location, false)); + self.handle_unused(unused); + } + + _ => (), + } + } + + /// Unify two types that should be the same. + /// Any unbound type variables will be linked to the other type as they are the same. + /// + /// It two types are found to not be the same an error is returned. + pub fn unify(&mut self, t1: Arc, t2: Arc, location: Span) -> Result<(), Error> { + if t1 == t2 { + return Ok(()); + } + + // Collapse right hand side type links. Left hand side will be collapsed in the next block. + if let Type::Var { tipo } = t2.deref() { + if let TypeVar::Link { tipo } = tipo.borrow().deref() { + return self.unify(t1, tipo.clone(), location); + } + } + + if let Type::Var { tipo } = t1.deref() { + enum Action { + Unify(Arc), + CouldNotUnify, + Link, + } + + let action = match tipo.borrow().deref() { + TypeVar::Link { tipo } => Action::Unify(tipo.clone()), + + TypeVar::Unbound { id } => { + unify_unbound_type(t2.clone(), *id, location)?; + Action::Link + } + + TypeVar::Generic { id } => { + if let Type::Var { tipo } = t2.deref() { + if tipo.borrow().is_unbound() { + *tipo.borrow_mut() = TypeVar::Generic { id: *id }; + return Ok(()); + } + } + Action::CouldNotUnify + } + }; + + return match action { + Action::Link => { + *tipo.borrow_mut() = TypeVar::Link { tipo: t2 }; + Ok(()) + } + + Action::Unify(t) => self.unify(t, t2, location), + + Action::CouldNotUnify => Err(Error::CouldNotUnify { + location, + expected: t1.clone(), + given: t2, + situation: None, + }), + }; + } + + if let Type::Var { .. } = t2.deref() { + return self.unify(t2, t1, location).map_err(|e| e.flip_unify()); + } + + match (t1.deref(), t2.deref()) { + ( + Type::App { + module: m1, + name: n1, + args: args1, + .. + }, + Type::App { + module: m2, + name: n2, + args: args2, + .. + }, + ) if m1 == m2 && n1 == n2 && args1.len() == args2.len() => { + for (a, b) in args1.iter().zip(args2) { + unify_enclosed_type( + t1.clone(), + t2.clone(), + self.unify(a.clone(), b.clone(), location), + )?; + } + Ok(()) + } + + ( + Type::Fn { + args: args1, + ret: retrn1, + .. + }, + Type::Fn { + args: args2, + ret: retrn2, + .. + }, + ) if args1.len() == args2.len() => { + for (a, b) in args1.iter().zip(args2) { + self.unify(a.clone(), b.clone(), location).map_err(|_| { + Error::CouldNotUnify { + location, + expected: t1.clone(), + given: t2.clone(), + situation: None, + } + })?; + } + self.unify(retrn1.clone(), retrn2.clone(), location) + .map_err(|_| Error::CouldNotUnify { + location, + expected: t1.clone(), + given: t2.clone(), + situation: None, + }) + } + + _ => Err(Error::CouldNotUnify { + location, + expected: t1.clone(), + given: t2.clone(), + situation: None, + }), + } + } + + /// Instantiate converts generic variables into unbound ones. + pub fn instantiate( + &mut self, + t: Arc, + ids: &mut HashMap>, + hydrator: &Hydrator, + ) -> Arc { + match t.deref() { + Type::App { + public, + name, + module, + args, + } => { + let args = args + .iter() + .map(|t| self.instantiate(t.clone(), ids, hydrator)) + .collect(); + Arc::new(Type::App { + public: *public, + name: name.clone(), + module: module.clone(), + args, + }) + } + + Type::Var { tipo } => { + match tipo.borrow().deref() { + TypeVar::Link { tipo } => return self.instantiate(tipo.clone(), ids, hydrator), + + TypeVar::Unbound { .. } => return Arc::new(Type::Var { tipo: tipo.clone() }), + + TypeVar::Generic { id } => match ids.get(id) { + Some(t) => return t.clone(), + None => { + if !hydrator.is_rigid(id) { + // Check this in the hydrator, i.e. is it a created type + let v = self.new_unbound_var(); + ids.insert(*id, v.clone()); + return v; + } else { + // tracing::trace!(id = id, "not_instantiating_rigid_type_var") + } + } + }, + } + Arc::new(Type::Var { tipo: tipo.clone() }) + } + + Type::Fn { args, ret, .. } => function( + args.iter() + .map(|t| self.instantiate(t.clone(), ids, hydrator)) + .collect(), + self.instantiate(ret.clone(), ids, hydrator), + ), + // Type::Tuple { elems } => tuple( + // elems + // .iter() + // .map(|t| self.instantiate(t.clone(), ids, hydrator)) + // .collect(), + // ), + } + } + + /// Create a new generic type that can stand in for any type. + pub fn new_generic_var(&mut self) -> Arc { + generic_var(self.next_uid()) + } + + /// Create a new unbound type that is a specific type, we just don't + /// know which one yet. + pub fn new_unbound_var(&mut self) -> Arc { + unbound_var(self.next_uid()) + } + + pub fn next_uid(&mut self) -> u64 { + let id = self.id_gen.next(); + self.previous_id = id; + id + } + + pub fn previous_uid(&self) -> u64 { + self.previous_id + } + + fn make_type_vars( + &mut self, + args: &[String], + location: &Span, + hydrator: &mut Hydrator, + ) -> Result>, Error> { + let mut type_vars = Vec::new(); + + for arg in args { + let annotation = Annotation::Var { + location: *location, + name: arg.to_string(), + }; + + let tipo = hydrator.type_from_annotation(&annotation, self)?; + + type_vars.push(tipo); + } + + Ok(type_vars) + } + + fn custom_type_accessors( + &mut self, + constructors: &[RecordConstructor], + hydrator: &mut Hydrator, + ) -> Result>, Error> { + let args = get_compatible_record_fields(constructors); + + let mut fields = HashMap::with_capacity(args.len()); + + hydrator.disallow_new_type_variables(); + + for (index, label, ast) in args { + let tipo = hydrator.type_from_annotation(ast, self)?; + + fields.insert( + label.to_string(), + RecordAccessor { + index: index as u64, + label: label.to_string(), + tipo, + }, + ); + } + Ok(Some(fields)) + } + + fn handle_unused(&mut self, unused: HashMap) { + for (name, (kind, location, _)) in unused.into_iter().filter(|(_, (_, _, used))| !used) { + let warning = match kind { + EntityKind::ImportedType | EntityKind::ImportedTypeAndConstructor => { + Warning::UnusedType { + name, + imported: true, + location, + } + } + EntityKind::ImportedConstructor => Warning::UnusedConstructor { + name, + imported: true, + location, + }, + EntityKind::PrivateConstant => { + Warning::UnusedPrivateModuleConstant { name, location } + } + EntityKind::PrivateTypeConstructor(_) => Warning::UnusedConstructor { + name, + imported: false, + location, + }, + EntityKind::PrivateFunction => Warning::UnusedPrivateFunction { name, location }, + EntityKind::PrivateType => Warning::UnusedType { + name, + imported: false, + location, + }, + EntityKind::ImportedValue => Warning::UnusedImportedValue { name, location }, + EntityKind::Variable => Warning::UnusedVariable { name, location }, + }; + + self.warnings.push(warning); + } + } +} + +/// For Keeping track of entity usages and knowing which error to display. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EntityKind { + PrivateConstant, + // String here is the type constructor's type name + PrivateTypeConstructor(String), + PrivateFunction, + ImportedConstructor, + ImportedType, + ImportedTypeAndConstructor, + ImportedValue, + PrivateType, + Variable, +} + +/// This function makes sure that the type variable being unified +/// doesn't occur within the type it is being unified with. This +/// prevents the algorithm from inferring recursive types, which +/// could cause naively-implemented type checking to diverge. +/// While traversing the type tree. +fn unify_unbound_type(tipo: Arc, own_id: u64, location: Span) -> Result<(), Error> { + if let Type::Var { tipo } = tipo.deref() { + let new_value = match tipo.borrow().deref() { + TypeVar::Link { tipo, .. } => { + return unify_unbound_type(tipo.clone(), own_id, location) + } + + TypeVar::Unbound { id } => { + if id == &own_id { + return Err(Error::RecursiveType { location }); + } else { + Some(TypeVar::Unbound { id: *id }) + } + } + + TypeVar::Generic { .. } => return Ok(()), + }; + + if let Some(t) = new_value { + *tipo.borrow_mut() = t; + } + return Ok(()); + } + + match tipo.deref() { + Type::App { args, .. } => { + for arg in args { + unify_unbound_type(arg.clone(), own_id, location)? + } + Ok(()) + } + + Type::Fn { args, ret } => { + for arg in args { + unify_unbound_type(arg.clone(), own_id, location)?; + } + unify_unbound_type(ret.clone(), own_id, location) + } + + Type::Var { .. } => unreachable!(), + } +} + +fn unify_enclosed_type( + e1: Arc, + e2: Arc, + result: Result<(), Error>, +) -> Result<(), Error> { + // If types cannot unify, show the type error with the enclosing types, e1 and e2. + match result { + Err(Error::CouldNotUnify { + situation, + location, + .. + }) => Err(Error::CouldNotUnify { + expected: e1, + given: e2, + situation, + location, + }), + + _ => result, + } +} + +fn assert_unique_type_name<'a>( + names: &mut HashMap<&'a str, &'a Span>, + name: &'a str, + location: &'a Span, +) -> Result<(), Error> { + match names.insert(name, location) { + Some(previous_location) => Err(Error::DuplicateTypeName { + name: name.to_string(), + previous_location: *previous_location, + location: *location, + }), + None => Ok(()), + } +} + +fn assert_unique_value_name<'a>( + names: &mut HashMap<&'a str, &'a Span>, + name: &'a str, + location: &'a Span, +) -> Result<(), Error> { + match names.insert(name, location) { + Some(previous_location) => Err(Error::DuplicateName { + name: name.to_string(), + previous_location: *previous_location, + location: *location, + }), + None => Ok(()), + } +} + +fn assert_unique_const_name<'a>( + names: &mut HashMap<&'a str, &'a Span>, + name: &'a str, + location: &'a Span, +) -> Result<(), Error> { + match names.insert(name, location) { + Some(previous_location) => Err(Error::DuplicateConstName { + name: name.to_string(), + previous_location: *previous_location, + location: *location, + }), + None => Ok(()), + } +} + +/// Returns the fields that have the same label and type across all variants of +/// the given type. +fn get_compatible_record_fields( + constructors: &[RecordConstructor], +) -> Vec<(usize, &str, &Annotation)> { + let mut compatible = vec![]; + + let first = match constructors.get(0) { + Some(first) => first, + None => return compatible, + }; + + 'next_argument: for (index, first_argument) in first.arguments.iter().enumerate() { + // Fields without labels do not have accessors + let label = match first_argument.label.as_ref() { + Some(label) => label.as_str(), + None => continue 'next_argument, + }; + + // Check each variant to see if they have an field in the same position + // with the same label and the same type + for constructor in constructors.iter().skip(1) { + // The field must exist in all variants + let argument = match constructor.arguments.get(index) { + Some(argument) => argument, + None => continue 'next_argument, + }; + + // The labels must be the same + if argument.label != first_argument.label { + continue 'next_argument; + } + + // The types must be the same + if !argument + .annotation + .is_logically_equal(&first_argument.annotation) + { + continue 'next_argument; + } + } + + // The previous loop did not find any incompatible fields in the other + // variants so this field is compatible across variants and we should + // generate an accessor for it. + compatible.push((index, label, &first_argument.annotation)) + } + + compatible +} diff --git a/crates/lang/src/tipo/error.rs b/crates/lang/src/tipo/error.rs index dc6add25..4ec544ea 100644 --- a/crates/lang/src/tipo/error.rs +++ b/crates/lang/src/tipo/error.rs @@ -2,12 +2,157 @@ use std::sync::Arc; use miette::Diagnostic; -use crate::ast::{Span, TodoKind}; +use crate::ast::{BinOp, Span, TodoKind}; use super::Type; #[derive(Debug, thiserror::Error, Diagnostic)] -pub enum Error {} +pub enum Error { + #[error("duplicate const {name}")] + DuplicateConstName { + #[label] + location: Span, + #[label] + previous_location: Span, + name: String, + }, + + #[error("duplicate import {name}")] + DuplicateImport { + #[label] + location: Span, + #[label] + previous_location: Span, + name: String, + }, + + #[error("duplicate name {label}")] + DuplicateField { + #[label] + location: Span, + label: String, + }, + + #[error("duplicate name {name}")] + DuplicateName { + #[label] + location: Span, + #[label] + previous_location: Span, + name: String, + }, + + #[error("duplicate type name {name}")] + DuplicateTypeName { + location: Span, + previous_location: Span, + name: String, + }, + + #[error("{name} has incorrect type arity expected {expected} but given {given}")] + IncorrectTypeArity { + location: Span, + name: String, + expected: usize, + given: usize, + }, + + #[error("{name} contains keyword {keyword}")] + KeywordInModuleName { name: String, keyword: String }, + + #[error("{name} is a reserved module name")] + ReservedModuleName { name: String }, + + #[error("unexpected type hole")] + UnexpectedTypeHole { + #[label] + location: Span, + }, + + #[error("unknown module {name}")] + UnknownModule { + location: Span, + name: String, + imported_modules: Vec, + }, + + #[error("unknown module field {name} in module {module_name}")] + UnknownModuleField { + location: Span, + name: String, + module_name: String, + value_constructors: Vec, + type_constructors: Vec, + }, + + #[error("")] + UnknownType { + location: Span, + name: String, + types: Vec, + }, + + #[error("")] + UnknownTypeConstructorType { + location: Span, + name: String, + type_constructors: Vec, + }, + + #[error("")] + UnknownTypeConstructorModule { + location: Span, + name: String, + imported_modules: Vec, + }, + + #[error("")] + UnknownTypeConstructorModuleType { + location: Span, + name: String, + module_name: Vec, + type_constructors: Vec, + }, + + #[error("")] + CouldNotUnify { + location: Span, + expected: Arc, + given: Arc, + situation: Option, + }, + + #[error("")] + ExtraVarInAlternativePattern { location: Span, name: String }, + + #[error("")] + MissingVarInAlternativePattern { location: Span, name: String }, + + #[error("")] + DuplicateVarInPattern { location: Span, name: String }, + + #[error("")] + RecursiveType { location: Span }, +} + +impl Error { + pub fn flip_unify(self) -> Error { + match self { + Error::CouldNotUnify { + location, + expected, + given, + situation: note, + } => Error::CouldNotUnify { + location, + expected: given, + given: expected, + situation: note, + }, + other => other, + } + } +} #[derive(Debug, PartialEq, Clone)] pub enum Warning { @@ -70,3 +215,24 @@ pub enum Warning { name: String, }, } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UnifyErrorSituation { + /// Clauses in a case expression were found to return different types. + CaseClauseMismatch, + + /// A function was found to return a value that did not match its return + /// annotation. + ReturnAnnotationMismatch, + + PipeTypeMismatch, + + /// The operands of a binary operator were incorrect. + Operator(BinOp), + + /// A try expression returned a different error type to the previous try. + TryErrorMismatch, + + /// The final value of a try expression was not a Result. + TryReturnResult, +} diff --git a/crates/lang/src/tipo/hydrator.rs b/crates/lang/src/tipo/hydrator.rs new file mode 100644 index 00000000..9db2028a --- /dev/null +++ b/crates/lang/src/tipo/hydrator.rs @@ -0,0 +1,207 @@ +use std::{collections::HashMap, sync::Arc}; + +use crate::{ast::Annotation, builtins::function}; + +use super::{environment::Environment, error::Error, Type, TypeConstructor}; + +/// The Hydrator takes an AST representing a type (i.e. a type annotation +/// for a function argument) and returns a Type for that annotation. +/// +/// If a valid Type cannot be constructed it returns an error. +/// +/// It keeps track of any type variables created. This is useful for: +/// +/// - Determining if a generic type variable should be made into an +/// unbound type varable during type instantiation. +/// - Ensuring that the same type is constructed if the programmer +/// uses the same name for a type variable multiple times. +/// +#[derive(Debug)] +pub struct Hydrator { + created_type_variables: HashMap>, + /// A rigid type is a generic type that was specified as being generic in + /// an annotation. As such it should never be instantiated into an unbound + /// variable. This type_id => name map is used for reporting the original + /// annotated name on error. + rigid_type_names: HashMap, + permit_new_type_variables: bool, + permit_holes: bool, +} + +#[derive(Debug)] +pub struct ScopeResetData { + created_type_variables: HashMap>, + rigid_type_names: HashMap, +} + +impl Hydrator { + pub fn new() -> Hydrator { + Hydrator { + created_type_variables: HashMap::new(), + rigid_type_names: HashMap::new(), + permit_new_type_variables: true, + permit_holes: false, + } + } + + pub fn open_new_scope(&mut self) -> ScopeResetData { + let created_type_variables = self.created_type_variables.clone(); + let rigid_type_names = self.rigid_type_names.clone(); + + ScopeResetData { + created_type_variables, + rigid_type_names, + } + } + + pub fn close_scope(&mut self, data: ScopeResetData) { + self.created_type_variables = data.created_type_variables; + self.rigid_type_names = data.rigid_type_names; + } + + pub fn disallow_new_type_variables(&mut self) { + self.permit_new_type_variables = false + } + + pub fn permit_holes(&mut self, flag: bool) { + self.permit_holes = flag + } + + /// A rigid type is a generic type that was specified as being generic in + /// an annotation. As such it should never be instantiated into an unbound + /// variable. + pub fn is_rigid(&self, id: &u64) -> bool { + self.rigid_type_names.contains_key(id) + } + + pub fn rigid_names(&self) -> HashMap { + self.rigid_type_names.clone() + } + + pub fn type_from_option_annotation<'a>( + &mut self, + ast: &Option, + environment: &mut Environment<'a>, + ) -> Result, Error> { + match ast { + Some(ast) => self.type_from_annotation(ast, environment), + None => Ok(environment.new_unbound_var()), + } + } + + /// Construct a Type from an AST Type annotation. + /// + pub fn type_from_annotation<'a>( + &mut self, + annotation: &Annotation, + environment: &mut Environment<'a>, + ) -> Result, Error> { + match annotation { + Annotation::Constructor { + location, + module, + name, + arguments: args, + } => { + // Hydrate the type argument AST into types + let mut argument_types = Vec::with_capacity(args.len()); + for t in args { + let typ = self.type_from_annotation(t, environment)?; + argument_types.push((t.location(), typ)); + } + + // Look up the constructor + let TypeConstructor { + parameters, + tipo: return_type, + .. + } = environment + .get_type_constructor(module, name, *location)? + .clone(); + + // Register the type constructor as being used if it is unqualifed. + // We do not track use of qualified type constructors as they may be + // used in another module. + if module.is_none() { + environment.increment_usage(name); + } + + // Ensure that the correct number of arguments have been given to the constructor + if args.len() != parameters.len() { + return Err(Error::IncorrectTypeArity { + location: *location, + name: name.to_string(), + expected: parameters.len(), + given: args.len(), + }); + } + + // Instantiate the constructor type for this specific usage + let mut type_vars = HashMap::new(); + #[allow(clippy::needless_collect)] // Not needless, used for size effects + let parameter_types: Vec<_> = parameters + .into_iter() + .map(|typ| environment.instantiate(typ, &mut type_vars, self)) + .collect(); + + let return_type = environment.instantiate(return_type, &mut type_vars, self); + + // Unify argument types with instantiated parameter types so that the correct types + // are inserted into the return type + for (parameter, (location, argument)) in + parameter_types.into_iter().zip(argument_types) + { + environment.unify(parameter, argument, location)?; + } + + Ok(return_type) + } + + Annotation::Fn { arguments, ret, .. } => { + let mut args = Vec::with_capacity(arguments.len()); + + for arg in arguments { + let arg = self.type_from_annotation(arg, environment)?; + + args.push(arg); + } + + let ret = self.type_from_annotation(ret, environment)?; + + Ok(function(args, ret)) + } + + Annotation::Var { name, location, .. } => match self.created_type_variables.get(name) { + Some(var) => Ok(var.clone()), + + None if self.permit_new_type_variables => { + let var = environment.new_generic_var(); + + self.rigid_type_names + .insert(environment.previous_uid(), name.clone()); + + self.created_type_variables + .insert(name.clone(), var.clone()); + + Ok(var) + } + + None => Err(Error::UnknownType { + name: name.to_string(), + location: *location, + types: environment + .module_types + .keys() + .map(|t| t.to_string()) + .collect(), + }), + }, + + Annotation::Hole { .. } if self.permit_holes => Ok(environment.new_unbound_var()), + + Annotation::Hole { location, .. } => Err(Error::UnexpectedTypeHole { + location: *location, + }), + } + } +} diff --git a/crates/lang/src/tipo/infer.rs b/crates/lang/src/tipo/infer.rs index 95007a24..e914d23c 100644 --- a/crates/lang/src/tipo/infer.rs +++ b/crates/lang/src/tipo/infer.rs @@ -2,10 +2,12 @@ use std::collections::HashMap; use crate::{ ast::{ModuleKind, TypedModule, UntypedModule}, + token::Token, IdGenerator, }; use super::{ + environment::Environment, error::{Error, Warning}, Module, }; @@ -18,5 +20,72 @@ pub fn module( modules: &HashMap, warnings: &mut Vec, ) -> Result { + let name = module.name.clone(); + let docs = std::mem::take(&mut module.docs); + let mut environment = Environment::new(id_gen.clone(), &name, modules, warnings); + + validate_module_name(&name)?; + + let mut type_names = HashMap::with_capacity(module.definitions.len()); + let mut value_names = HashMap::with_capacity(module.definitions.len()); + let mut hydrators = HashMap::with_capacity(module.definitions.len()); + + // Register any modules, types, and values being imported + // We process imports first so that anything imported can be referenced + // anywhere in the module. + for def in module.definitions() { + environment.register_import(def)?; + } + + // Register types so they can be used in constructors and functions + // earlier in the module. + for def in module.definitions() { + environment.register_types(def, &name, &mut hydrators, &mut type_names)?; + } + + // Register values so they can be used in functions earlier in the module. + for def in module.definitions() { + environment.register_values(def, &name, &mut hydrators, &mut value_names)?; + } + todo!() } + +fn validate_module_name(name: &str) -> Result<(), Error> { + if name == "aiken" { + return Err(Error::ReservedModuleName { + name: name.to_string(), + }); + }; + + for segment in name.split('/') { + if str_to_keyword(segment).is_some() { + return Err(Error::KeywordInModuleName { + name: name.to_string(), + keyword: segment.to_string(), + }); + } + } + + Ok(()) +} + +pub fn str_to_keyword(word: &str) -> Option { + // Alphabetical keywords: + match word { + "as" => Some(Token::As), + "assert" => Some(Token::Assert), + "when" => Some(Token::When), + "const" => Some(Token::Const), + "fn" => Some(Token::Fn), + "if" => Some(Token::If), + "use" => Some(Token::Use), + "let" => Some(Token::Let), + "opaque" => Some(Token::Opaque), + "pub" => Some(Token::Pub), + "todo" => Some(Token::Todo), + "try" => Some(Token::Try), + "type" => Some(Token::Type), + _ => None, + } +}