From cabc6531678da5a565cdc688afadb82a7b3709b2 Mon Sep 17 00:00:00 2001 From: rvcas Date: Wed, 19 Oct 2022 23:23:23 -0400 Subject: [PATCH] feat: start expr inference --- Cargo.lock | 16 + crates/cli/src/project.rs | 35 +- crates/lang/Cargo.toml | 1 + crates/lang/src/ast.rs | 132 ++-- crates/lang/src/builtins.rs | 21 +- crates/lang/src/expr.rs | 118 +++- crates/lang/src/tipo.rs | 228 ++++++- crates/lang/src/tipo/environment.rs | 942 ++++++++++++++++++---------- crates/lang/src/tipo/error.rs | 169 ++++- crates/lang/src/tipo/expr.rs | 597 ++++++++++++++++++ crates/lang/src/tipo/fields.rs | 147 +++++ crates/lang/src/tipo/infer.rs | 439 +++++++++++-- 12 files changed, 2373 insertions(+), 472 deletions(-) create mode 100644 crates/lang/src/tipo/expr.rs create mode 100644 crates/lang/src/tipo/fields.rs diff --git a/Cargo.lock b/Cargo.lock index fd9ce8b6..e7f4986b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,7 @@ version = "0.0.20" dependencies = [ "chumsky", "internment", + "itertools", "miette", "pretty_assertions", "thiserror", @@ -294,6 +295,12 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + [[package]] name = "fastrand" version = "1.8.0" @@ -443,6 +450,15 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.3" diff --git a/crates/cli/src/project.rs b/crates/cli/src/project.rs index 7d311904..7d6c72b8 100644 --- a/crates/cli/src/project.rs +++ b/crates/cli/src/project.rs @@ -4,7 +4,12 @@ use std::{ path::{Path, PathBuf}, }; -use aiken_lang::{ast::ModuleKind, builtins, tipo, IdGenerator}; +use aiken_lang::{ + ast::ModuleKind, + builtins, + tipo::{self, TypeInfo}, + IdGenerator, +}; use crate::{ config::Config, @@ -39,7 +44,7 @@ pub struct Project { config: Config, defined_modules: HashMap, id_gen: IdGenerator, - module_types: HashMap, + module_types: HashMap, root: PathBuf, sources: Vec, pub warnings: Vec, @@ -164,19 +169,19 @@ impl Project { { let mut type_warnings = Vec::new(); - let ast = tipo::infer::module( - &self.id_gen, - ast, - kind, - &self.config.name, - &self.module_types, - &mut type_warnings, - ) - .map_err(|error| Error::Type { - path: path.clone(), - src: code.clone(), - error, - })?; + let ast = ast + .infer( + &self.id_gen, + kind, + &self.config.name, + &self.module_types, + &mut type_warnings, + ) + .map_err(|error| Error::Type { + path: path.clone(), + src: code.clone(), + error, + })?; // Register any warnings emitted as type warnings let type_warnings = type_warnings diff --git a/crates/lang/Cargo.toml b/crates/lang/Cargo.toml index fdb58526..f834da6b 100644 --- a/crates/lang/Cargo.toml +++ b/crates/lang/Cargo.toml @@ -13,6 +13,7 @@ authors = ["Lucas Rosa ", "Kasey White "] [dependencies] chumsky = "0.8.0" internment = "0.7.0" +itertools = "0.10.5" miette = "5.2.0" thiserror = "1.0.37" vec1 = "1.8.0" diff --git a/crates/lang/src/ast.rs b/crates/lang/src/ast.rs index 2c417503..2769a127 100644 --- a/crates/lang/src/ast.rs +++ b/crates/lang/src/ast.rs @@ -1,15 +1,19 @@ -use std::{collections::HashMap, fmt, ops::Range, sync::Arc}; +use std::{fmt, ops::Range, sync::Arc}; use internment::Intern; use crate::{ + builtins, expr::{TypedExpr, UntypedExpr}, - tipo::{self, PatternConstructor, Type, ValueConstructor}, + tipo::{fields::FieldMap, PatternConstructor, Type, TypeInfo, ValueConstructor}, }; +pub const ASSERT_VARIABLE: &str = "_try"; pub const CAPTURE_VARIABLE: &str = "_capture"; +pub const PIPE_VARIABLE: &str = "_pipe"; +pub const TRY_VARIABLE: &str = "_try"; -pub type TypedModule = Module; +pub type TypedModule = Module; pub type UntypedModule = Module<(), UntypedDefinition>; #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -133,11 +137,6 @@ pub enum Constant { value: String, }, - Pair { - location: Span, - elements: Vec, - }, - List { location: Span, elements: Vec, @@ -154,9 +153,9 @@ pub enum Constant { field_map: Option, }, - ByteString { + ByteArray { location: Span, - // segments: Vec>, + bytes: Vec, }, Var { @@ -168,6 +167,39 @@ pub enum Constant { }, } +impl TypedConstant { + pub fn tipo(&self) -> Arc { + match self { + Constant::Int { .. } => builtins::int(), + Constant::String { .. } => builtins::string(), + Constant::ByteArray { .. } => builtins::byte_array(), + Constant::List { tipo, .. } + | Constant::Record { tipo, .. } + | Constant::Var { tipo, .. } => tipo.clone(), + } + } +} + +impl Constant { + pub fn location(&self) -> Span { + match self { + Constant::Int { location, .. } + | Constant::List { location, .. } + | Constant::String { location, .. } + | Constant::Record { location, .. } + | Constant::ByteArray { location, .. } + | Constant::Var { location, .. } => *location, + } + } + + pub fn is_simple(&self) -> bool { + matches!( + self, + Self::Int { .. } | Self::ByteArray { .. } | Self::String { .. } + ) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct CallArg { pub label: Option, @@ -175,44 +207,6 @@ pub struct CallArg { pub value: A, } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct FieldMap { - pub arity: usize, - 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, @@ -232,6 +226,7 @@ pub struct RecordConstructorArg { pub doc: Option, } +pub type TypedArg = Arg>; pub type UntypedArg = Arg<()>; #[derive(Debug, Clone, PartialEq)] @@ -242,6 +237,21 @@ pub struct Arg { pub tipo: T, } +impl Arg { + pub fn set_type(self, tipo: B) -> Arg { + Arg { + tipo, + arg_name: self.arg_name, + location: self.location, + annotation: self.annotation, + } + } + + pub fn get_variable_name(&self) -> Option<&str> { + self.arg_name.get_variable_name() + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum ArgName { Discard { @@ -264,6 +274,15 @@ pub enum ArgName { }, } +impl ArgName { + pub fn get_variable_name(&self) -> Option<&str> { + match self { + ArgName::Discard { .. } | ArgName::LabeledDiscard { .. } => None, + ArgName::NamedLabeled { name, .. } | ArgName::Named { name, .. } => Some(name), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct UnqualifiedImport { pub location: Span, @@ -272,6 +291,16 @@ pub struct UnqualifiedImport { pub layer: Layer, } +impl UnqualifiedImport { + pub fn variable_name(&self) -> &str { + self.as_name.as_deref().unwrap_or(&self.name) + } + + pub fn is_value(&self) -> bool { + self.layer.is_value() + } +} + // TypeAst #[derive(Debug, Clone, PartialEq)] pub enum Annotation { @@ -383,6 +412,13 @@ impl Default for Layer { } } +impl Layer { + /// Returns `true` if the layer is [`Value`]. + pub fn is_value(&self) -> bool { + matches!(self, Self::Value) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BinOp { // Boolean logic diff --git a/crates/lang/src/builtins.rs b/crates/lang/src/builtins.rs index 864766cc..43fea17a 100644 --- a/crates/lang/src/builtins.rs +++ b/crates/lang/src/builtins.rs @@ -2,7 +2,10 @@ use std::{cell::RefCell, collections::HashMap, sync::Arc}; use crate::{ ast::{ModuleKind, Span}, - tipo::{self, Type, TypeConstructor, TypeVar, ValueConstructor, ValueConstructorVariant}, + tipo::{ + self, fields::FieldMap, Type, TypeConstructor, TypeVar, ValueConstructor, + ValueConstructorVariant, + }, IdGenerator, }; @@ -16,9 +19,9 @@ const STRING: &str = "String"; /// Build a prelude that can be injected /// into a compiler pipeline -pub fn prelude(id_gen: &IdGenerator) -> tipo::Module { - let mut prelude = tipo::Module { - name: vec!["aiken".to_string()], +pub fn prelude(id_gen: &IdGenerator) -> tipo::TypeInfo { + let mut prelude = tipo::TypeInfo { + name: "aiken".to_string(), package: "".to_string(), kind: ModuleKind::Lib, types: HashMap::new(), @@ -64,7 +67,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module { ValueConstructorVariant::Record { module: "".into(), name: "True".to_string(), - field_map: None, + field_map: None::, arity: 0, location: Span::empty(), constructors_count: 2, @@ -79,7 +82,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module { ValueConstructorVariant::Record { module: "".into(), name: "False".to_string(), - field_map: None, + field_map: None::, arity: 0, location: Span::empty(), constructors_count: 2, @@ -132,7 +135,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module { module: "".into(), name: NIL.to_string(), arity: 0, - field_map: None, + field_map: None::, location: Span::empty(), constructors_count: 1, }, @@ -179,7 +182,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module { ValueConstructorVariant::Record { module: "".into(), name: "Ok".to_string(), - field_map: None, + field_map: None::, arity: 1, location: Span::empty(), constructors_count: 2, @@ -196,7 +199,7 @@ pub fn prelude(id_gen: &IdGenerator) -> tipo::Module { ValueConstructorVariant::Record { module: "".into(), name: "Error".to_string(), - field_map: None, + field_map: None::, arity: 1, location: Span::empty(), constructors_count: 2, diff --git a/crates/lang/src/expr.rs b/crates/lang/src/expr.rs index fd512e51..99b7343b 100644 --- a/crates/lang/src/expr.rs +++ b/crates/lang/src/expr.rs @@ -7,6 +7,7 @@ use crate::{ Annotation, Arg, AssignmentKind, BinOp, CallArg, Clause, IfBranch, Pattern, RecordUpdateSpread, Span, TodoKind, TypedRecordUpdateArg, UntypedRecordUpdateArg, }, + builtins::{bool, nil}, tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor}, }; @@ -18,16 +19,16 @@ pub enum TypedExpr { value: String, }, - Float { + String { location: Span, tipo: Arc, value: String, }, - String { + ByteArray { location: Span, tipo: Arc, - value: String, + bytes: Vec, }, Sequence { @@ -161,6 +162,101 @@ pub enum TypedExpr { }, } +impl TypedExpr { + pub fn tipo(&self) -> Arc { + match self { + Self::Negate { .. } => bool(), + Self::Var { constructor, .. } => constructor.tipo.clone(), + Self::Try { then, .. } => then.tipo(), + Self::Fn { tipo, .. } + | Self::Int { tipo, .. } + | Self::Todo { tipo, .. } + | Self::When { tipo, .. } + | Self::List { tipo, .. } + | Self::Call { tipo, .. } + | Self::If { tipo, .. } + | Self::BinOp { tipo, .. } + | Self::Tuple { tipo, .. } + | Self::String { tipo, .. } + | Self::ByteArray { tipo, .. } + | Self::TupleIndex { tipo, .. } + | Self::Assignment { tipo, .. } + | Self::ModuleSelect { tipo, .. } + | Self::RecordAccess { tipo, .. } + | Self::RecordUpdate { tipo, .. } => tipo.clone(), + Self::Pipeline { expressions, .. } | Self::Sequence { expressions, .. } => { + expressions.last().map(TypedExpr::tipo).unwrap_or_else(nil) + } + } + } + + pub fn type_defining_location(&self) -> Span { + match self { + Self::Fn { location, .. } + | Self::Int { location, .. } + | Self::Try { location, .. } + | Self::Var { location, .. } + | Self::Todo { location, .. } + | Self::When { location, .. } + | Self::Call { location, .. } + | Self::List { location, .. } + | Self::BinOp { location, .. } + | Self::Tuple { location, .. } + | Self::String { location, .. } + | Self::Negate { location, .. } + | Self::Pipeline { location, .. } + | Self::ByteArray { location, .. } + | Self::Assignment { location, .. } + | Self::TupleIndex { location, .. } + | Self::ModuleSelect { location, .. } + | Self::RecordAccess { location, .. } + | Self::RecordUpdate { location, .. } => *location, + + Self::If { + location, + branches, + final_else, + tipo, + } => branches.first().body.type_defining_location(), + + Self::Sequence { + expressions, + location, + .. + } => expressions + .last() + .map(TypedExpr::location) + .unwrap_or(*location), + } + } + + pub fn location(&self) -> Span { + match self { + Self::Fn { location, .. } + | Self::Try { location, .. } + | Self::Int { location, .. } + | Self::Var { location, .. } + | Self::Todo { location, .. } + | Self::When { location, .. } + | Self::Call { location, .. } + | Self::If { location, .. } + | Self::List { location, .. } + | Self::BinOp { location, .. } + | Self::Tuple { location, .. } + | Self::String { location, .. } + | Self::Negate { location, .. } + | Self::Sequence { location, .. } + | Self::Pipeline { location, .. } + | Self::ByteArray { location, .. } + | Self::Assignment { location, .. } + | Self::TupleIndex { location, .. } + | Self::ModuleSelect { location, .. } + | Self::RecordAccess { location, .. } + | Self::RecordUpdate { location, .. } => *location, + } + } +} + #[derive(Debug, Clone, PartialEq)] pub enum UntypedExpr { Int { @@ -168,11 +264,6 @@ pub enum UntypedExpr { value: String, }, - Float { - location: Span, - value: String, - }, - String { location: Span, value: String, @@ -203,9 +294,9 @@ pub enum UntypedExpr { }, Call { - location: Span, - fun: Box, arguments: Vec>, + fun: Box, + location: Span, }, BinOp { @@ -215,6 +306,11 @@ pub enum UntypedExpr { right: Box, }, + ByteArray { + location: Span, + bytes: Vec, + }, + PipeLine { expressions: Vec1, }, @@ -344,7 +440,7 @@ impl UntypedExpr { | Self::When { location, .. } | Self::Call { location, .. } | Self::List { location, .. } - | Self::Float { location, .. } + | Self::ByteArray { location, .. } | Self::BinOp { location, .. } | Self::Tuple { location, .. } | Self::String { location, .. } diff --git a/crates/lang/src/tipo.rs b/crates/lang/src/tipo.rs index 1024ef00..11fbb9aa 100644 --- a/crates/lang/src/tipo.rs +++ b/crates/lang/src/tipo.rs @@ -1,11 +1,18 @@ -use std::{cell::RefCell, collections::HashMap, sync::Arc}; +use std::{cell::RefCell, collections::HashMap, ops::Deref, sync::Arc}; -use crate::ast::{Constant, FieldMap, ModuleKind, Span, TypedConstant}; +use crate::{ + ast::{Constant, ModuleKind, Span, TypedConstant}, + tipo::fields::FieldMap, +}; + +use self::environment::Environment; mod environment; pub mod error; +mod expr; +pub mod fields; mod hydrator; -pub mod infer; +mod infer; #[derive(Debug, Clone, PartialEq)] pub enum Type { @@ -41,6 +48,161 @@ pub enum Type { // Tuple { elems: Vec> }, } +impl Type { + pub fn is_result_constructor(&self) -> bool { + match self { + Type::Fn { ret, .. } => ret.is_result(), + _ => false, + } + } + + pub fn is_result(&self) -> bool { + matches!(self, Self::App { name, module, .. } if "Result" == name && module.is_empty()) + } + + pub fn is_unbound(&self) -> bool { + matches!(self, Self::Var { tipo } if tipo.borrow().is_unbound()) + } + + pub fn return_type(&self) -> Option> { + match self { + Self::Fn { ret, .. } => Some(ret.clone()), + _ => None, + } + } + + pub fn function_types(&self) -> Option<(Vec>, Arc)> { + match self { + Self::Fn { args, ret, .. } => Some((args.clone(), ret.clone())), + _ => None, + } + } + + pub fn is_nil(&self) -> bool { + match self { + Self::App { module, name, .. } if "Nil" == name && module.is_empty() => true, + Self::Var { tipo } => tipo.borrow().is_nil(), + _ => false, + } + } + + pub fn is_bool(&self) -> bool { + match self { + Self::App { module, name, .. } if "Bool" == name && module.is_empty() => true, + Self::Var { tipo } => tipo.borrow().is_bool(), + _ => false, + } + } + + pub fn is_int(&self) -> bool { + match self { + Self::App { module, name, .. } if "Int" == name && module.is_empty() => true, + Self::Var { tipo } => tipo.borrow().is_int(), + _ => false, + } + } + + pub fn is_bytearray(&self) -> bool { + match self { + Self::App { module, name, .. } if "ByteArray" == name && module.is_empty() => true, + Self::Var { tipo } => tipo.borrow().is_bytearray(), + _ => false, + } + } + + pub fn is_string(&self) -> bool { + match self { + Self::App { module, name, .. } if "String" == name && module.is_empty() => true, + Self::Var { tipo } => tipo.borrow().is_string(), + _ => false, + } + } + + /// Get the args for the type if the type is a specific `Type::App`. + /// Returns None if the type is not a `Type::App` or is an incorrect `Type:App` + /// + /// This function is currently only used for finding the `List` type. + pub fn get_app_args( + &self, + public: bool, + module: &String, + name: &str, + arity: usize, + environment: &mut Environment<'_>, + ) -> Option>> { + match self { + Self::App { + module: m, + name: n, + args, + .. + } => { + if module == m && name == n && args.len() == arity { + Some(args.clone()) + } else { + None + } + } + + Self::Var { tipo } => { + let args: Vec<_> = match tipo.borrow().deref() { + TypeVar::Link { tipo } => { + return tipo.get_app_args(public, module, name, arity, environment); + } + + TypeVar::Unbound { .. } => { + (0..arity).map(|_| environment.new_unbound_var()).collect() + } + + TypeVar::Generic { .. } => return None, + }; + + // We are an unbound type variable! So convert us to a type link + // to the desired type. + *tipo.borrow_mut() = TypeVar::Link { + tipo: Arc::new(Self::App { + name: name.to_string(), + module: module.to_owned(), + args: args.clone(), + public, + }), + }; + Some(args) + } + + _ => None, + } + } + + pub fn find_private_type(&self) -> Option { + match self { + Self::App { public: false, .. } => Some(self.clone()), + + Self::App { args, .. } => args.iter().find_map(|t| t.find_private_type()), + + // Self::Tuple { elems, .. } => elems.iter().find_map(|t| t.find_private_type()), + Self::Fn { ret, args, .. } => ret + .find_private_type() + .or_else(|| args.iter().find_map(|t| t.find_private_type())), + + Self::Var { tipo, .. } => match tipo.borrow().deref() { + TypeVar::Unbound { .. } => None, + + TypeVar::Generic { .. } => None, + + TypeVar::Link { tipo, .. } => tipo.find_private_type(), + }, + } + } + + pub fn fn_arity(&self) -> Option { + match self { + Self::Fn { args, .. } => Some(args.len()), + _ => None, + } + } +} + #[derive(Debug, Clone, PartialEq)] pub enum TypeVar { /// Unbound is an unbound variable. It is one specific type but we don't @@ -72,6 +234,41 @@ impl TypeVar { pub fn is_unbound(&self) -> bool { matches!(self, Self::Unbound { .. }) } + + pub fn is_nil(&self) -> bool { + match self { + Self::Link { tipo } => tipo.is_nil(), + _ => false, + } + } + + pub fn is_bool(&self) -> bool { + match self { + Self::Link { tipo } => tipo.is_bool(), + _ => false, + } + } + + pub fn is_int(&self) -> bool { + match self { + Self::Link { tipo } => tipo.is_int(), + _ => false, + } + } + + pub fn is_bytearray(&self) -> bool { + match self { + Self::Link { tipo } => tipo.is_bytearray(), + _ => false, + } + } + + pub fn is_string(&self) -> bool { + match self { + Self::Link { tipo } => tipo.is_string(), + _ => false, + } + } } #[derive(Debug, Clone, PartialEq)] @@ -89,6 +286,14 @@ impl ValueConstructor { tipo, } } + + fn field_map(&self) -> Option<&FieldMap> { + match &self.variant { + ValueConstructorVariant::ModuleFn { field_map, .. } + | ValueConstructorVariant::Record { field_map, .. } => field_map.as_ref(), + _ => None, + } + } } #[derive(Debug, Clone, PartialEq)] @@ -123,9 +328,20 @@ pub enum ValueConstructorVariant { }, } +impl ValueConstructorVariant { + pub fn location(&self) -> Span { + match self { + ValueConstructorVariant::LocalVariable { location } + | ValueConstructorVariant::ModuleConstant { location, .. } + | ValueConstructorVariant::ModuleFn { location, .. } + | ValueConstructorVariant::Record { location, .. } => *location, + } + } +} + #[derive(Debug, Clone)] -pub struct Module { - pub name: Vec, +pub struct TypeInfo { + pub name: String, pub kind: ModuleKind, pub package: String, pub types: HashMap, @@ -171,7 +387,7 @@ pub enum ModuleValueConstructor { Record { name: String, arity: usize, - type_: Arc, + tipo: Arc, field_map: Option, location: Span, }, diff --git a/crates/lang/src/tipo/environment.rs b/crates/lang/src/tipo/environment.rs index 040c5859..0837bedf 100644 --- a/crates/lang/src/tipo/environment.rs +++ b/crates/lang/src/tipo/environment.rs @@ -6,20 +6,26 @@ use std::{ use crate::{ ast::{ - Annotation, ArgName, Definition, FieldMap, RecordConstructor, RecordConstructorArg, Span, - UnqualifiedImport, UntypedDefinition, + Annotation, ArgName, CallArg, Definition, RecordConstructor, RecordConstructorArg, Span, + TypedDefinition, UnqualifiedImport, UntypedDefinition, PIPE_VARIABLE, }, builtins::{function, generic_var, unbound_var}, + tipo::fields::FieldMap, IdGenerator, }; use super::{ error::{Error, Warning}, hydrator::Hydrator, - AccessorsMap, Module, RecordAccessor, Type, TypeConstructor, TypeVar, ValueConstructor, + AccessorsMap, RecordAccessor, Type, TypeConstructor, TypeInfo, TypeVar, ValueConstructor, ValueConstructorVariant, }; +#[derive(Debug)] +pub struct ScopeResetData { + local_values: HashMap, +} + #[derive(Debug)] pub struct Environment<'a> { /// Accessors defined in the current module @@ -31,11 +37,11 @@ pub struct Environment<'a> { /// 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, + 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_modules: HashMap, pub imported_types: HashSet, /// Types defined in the current module (or the prelude) @@ -68,10 +74,525 @@ pub struct Environment<'a> { } impl<'a> Environment<'a> { + pub fn close_scope(&mut self, data: ScopeResetData) { + let unused = self + .entity_usages + .pop() + .expect("There was no top entity scope."); + + self.handle_unused(unused); + + self.scope = data.local_values; + } + + /// Converts entities with a usage count of 0 to warnings + pub fn convert_unused_to_warnings(&mut self) { + let unused = self + .entity_usages + .pop() + .expect("Expected a bottom level of entity usages."); + + self.handle_unused(unused); + + for (name, location) in self.unused_modules.clone().into_iter() { + self.warnings + .push(Warning::UnusedImportedModule { name, location }); + } + } + + pub fn match_fun_type( + &mut self, + tipo: Arc, + arity: usize, + fn_location: Span, + call_location: Span, + ) -> Result<(Vec>, Arc), Error> { + if let Type::Var { tipo } = tipo.deref() { + let new_value = match tipo.borrow().deref() { + TypeVar::Link { tipo, .. } => { + return self.match_fun_type(tipo.clone(), arity, fn_location, call_location); + } + + TypeVar::Unbound { .. } => { + let args: Vec<_> = (0..arity).map(|_| self.new_unbound_var()).collect(); + + let ret = self.new_unbound_var(); + + Some((args, ret)) + } + + TypeVar::Generic { .. } => None, + }; + + if let Some((args, ret)) = new_value { + *tipo.borrow_mut() = TypeVar::Link { + tipo: function(args.clone(), ret.clone()), + }; + + return Ok((args, ret)); + } + } + + if let Type::Fn { args, ret } = tipo.deref() { + return if args.len() != arity { + Err(Error::IncorrectArity { + expected: args.len(), + given: arity, + labels: vec![], + location: call_location, + }) + } else { + Ok((args.clone(), ret.clone())) + }; + } + + Err(Error::NotFn { + tipo, + location: fn_location, + }) + } + + 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)) + } + + pub fn generalise_definition( + &mut self, + s: TypedDefinition, + module_name: &String, + ) -> TypedDefinition { + match s { + Definition::Fn { + doc, + location, + name, + public, + arguments: args, + body, + return_annotation, + return_type, + } => { + // Lookup the inferred function information + let function = self + .get_variable(&name) + .expect("Could not find preregistered type for function"); + + let field_map = function.field_map().cloned(); + + let tipo = function.tipo.clone(); + + // Generalise the function if not already done so + let tipo = if self.ungeneralised_functions.remove(&name) { + generalise(tipo, 0) + } else { + tipo + }; + + // Insert the function into the module's interface + self.insert_module_value( + &name, + ValueConstructor { + public, + tipo, + variant: ValueConstructorVariant::ModuleFn { + name: name.clone(), + field_map, + module: module_name.to_owned(), + arity: args.len(), + location, + }, + }, + ); + + Definition::Fn { + doc, + location, + name, + public, + arguments: args, + return_annotation, + return_type, + body, + } + } + + definition @ (Definition::TypeAlias { .. } + | Definition::DataType { .. } + | Definition::Use { .. } + | Definition::ModuleConstant { .. }) => definition, + } + } + + /// 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::UnknownType { + location, + name: name.to_string(), + types: self.module_types.keys().map(|t| t.to_string()).collect(), + }), + + Some(m) => { + let (_, module) = + self.imported_modules + .get(m) + .ok_or_else(|| Error::UnknownModule { + 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::UnknownModuleType { + location, + name: name.to_string(), + module_name: module.name.clone(), + type_constructors: module.types.keys().map(|t| t.to_string()).collect(), + }) + } + } + } + + /// Lookup a value constructor in the current scope. + /// + pub fn get_value_constructor( + &mut self, + module: Option<&String>, + name: &str, + location: Span, + ) -> Result<&ValueConstructor, Error> { + match module { + None => self.scope.get(name).ok_or_else(|| Error::UnknownVariable { + name: name.to_string(), + variables: self.local_value_names(), + location, + }), + + Some(m) => { + let (_, module) = + self.imported_modules + .get(m) + .ok_or_else(|| Error::UnknownModule { + name: name.to_string(), + imported_modules: self + .importable_modules + .keys() + .map(|t| t.to_string()) + .collect(), + location, + })?; + + self.unused_modules.remove(m); + + module + .values + .get(name) + .ok_or_else(|| Error::UnknownModuleValue { + name: name.to_string(), + module_name: module.name.clone(), + value_constructors: module.values.keys().map(|t| t.to_string()).collect(), + location, + }) + } + } + } + + /// Lookup a variable in the current scope. + pub fn get_variable(&self, name: &str) -> Option<&ValueConstructor> { + self.scope.get(name) + } + + 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); + } + } + + pub fn in_new_scope(&mut self, process_scope: impl FnOnce(&mut Self) -> T) -> T { + // Record initial scope state + let initial = self.open_new_scope(); + + // Process scope + let result = process_scope(self); + + self.close_scope(initial); + + // Return result of typing the scope + result + } + + /// 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); + } + + _ => (), + } + } + + 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); + } + + /// 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); + } + + /// 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, + }, + ); + } + + /// 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(), + // ), + } + } + + pub fn local_value_names(&self) -> Vec { + self.scope + .keys() + .filter(|&t| PIPE_VARIABLE != t) + .map(|t| t.to_string()) + .collect() + } + + 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) + } + pub fn new( id_gen: IdGenerator, current_module: &'a String, - importable_modules: &'a HashMap, + importable_modules: &'a HashMap, warnings: &'a mut Vec, ) -> Self { let prelude = importable_modules @@ -98,6 +619,35 @@ impl<'a> Environment<'a> { } } + /// 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 open_new_scope(&mut self) -> ScopeResetData { + let local_values = self.scope.clone(); + + self.entity_usages.push(HashMap::new()); + + ScopeResetData { local_values } + } + + pub fn previous_uid(&self) -> u64 { + self.previous_id + } + pub fn register_import(&mut self, def: &UntypedDefinition) -> Result<(), Error> { match def { Definition::Use { @@ -532,154 +1082,6 @@ impl<'a> Environment<'a> { 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. /// @@ -735,6 +1137,7 @@ impl<'a> Environment<'a> { expected: t1.clone(), given: t2, situation: None, + rigid_type_names: HashMap::new(), }), }; } @@ -787,6 +1190,7 @@ impl<'a> Environment<'a> { expected: t1.clone(), given: t2.clone(), situation: None, + rigid_type_names: HashMap::new(), } })?; } @@ -796,6 +1200,7 @@ impl<'a> Environment<'a> { expected: t1.clone(), given: t2.clone(), situation: None, + rigid_type_names: HashMap::new(), }) } @@ -804,179 +1209,10 @@ impl<'a> Environment<'a> { expected: t1.clone(), given: t2.clone(), situation: None, + rigid_type_names: HashMap::new(), }), } } - - /// 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. @@ -1052,12 +1288,14 @@ fn unify_enclosed_type( Err(Error::CouldNotUnify { situation, location, + rigid_type_names, .. }) => Err(Error::CouldNotUnify { expected: e1, given: e2, situation, location, + rigid_type_names, }), _ => result, @@ -1109,6 +1347,18 @@ fn assert_unique_const_name<'a>( } } +pub(super) fn assert_no_labeled_arguments(args: &[CallArg]) -> Result<(), Error> { + for arg in args { + if let Some(label) = &arg.label { + return Err(Error::UnexpectedLabeledArg { + location: arg.location, + label: label.to_string(), + }); + } + } + Ok(()) +} + /// Returns the fields that have the same label and type across all variants of /// the given type. fn get_compatible_record_fields( @@ -1159,3 +1409,47 @@ fn get_compatible_record_fields( compatible } + +/// Takes a level and a type and turns all type variables within the type that have +/// level higher than the input level into generalized (polymorphic) type variables. +pub(crate) fn generalise(t: Arc, ctx_level: usize) -> Arc { + match t.deref() { + Type::Var { tipo } => match tipo.borrow().deref() { + TypeVar::Unbound { id } => generic_var(*id), + TypeVar::Link { tipo } => generalise(tipo.clone(), ctx_level), + TypeVar::Generic { .. } => Arc::new(Type::Var { tipo: tipo.clone() }), + }, + + Type::App { + public, + module, + name, + args, + } => { + let args = args + .iter() + .map(|t| generalise(t.clone(), ctx_level)) + .collect(); + + Arc::new(Type::App { + public: *public, + module: module.clone(), + name: name.clone(), + args, + }) + } + + Type::Fn { args, ret } => function( + args.iter() + .map(|t| generalise(t.clone(), ctx_level)) + .collect(), + generalise(ret.clone(), ctx_level), + ), + // Type::Tuple { elems } => tuple( + // elems + // .iter() + // .map(|t| generalise(t.clone(), ctx_level)) + // .collect(), + // ), + } +} diff --git a/crates/lang/src/tipo/error.rs b/crates/lang/src/tipo/error.rs index 4ec544ea..c7b7f7e5 100644 --- a/crates/lang/src/tipo/error.rs +++ b/crates/lang/src/tipo/error.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use miette::Diagnostic; @@ -6,8 +6,19 @@ use crate::ast::{BinOp, Span, TodoKind}; use super::Type; +// use aiken/pub + +// pub fn do_thing() { pub.other() } + #[derive(Debug, thiserror::Error, Diagnostic)] pub enum Error { + #[error("duplicate argument {label}")] + DuplicateArgument { + #[label] + location: Span, + label: String, + }, + #[error("duplicate const {name}")] DuplicateConstName { #[label] @@ -49,6 +60,15 @@ pub enum Error { name: String, }, + #[error("incorrect arity expected {expected} but given {given}")] + IncorrectArity { + #[label] + location: Span, + expected: usize, + given: usize, + labels: Vec, + }, + #[error("{name} has incorrect type arity expected {expected} but given {given}")] IncorrectTypeArity { location: Span, @@ -57,18 +77,59 @@ pub enum Error { given: usize, }, + #[error("not a function")] + NotFn { + #[label] + location: Span, + tipo: Arc, + }, + #[error("{name} contains keyword {keyword}")] KeywordInModuleName { name: String, keyword: String }, + #[error("clause guard {name} is not local")] + NonLocalClauseGuardVariable { + #[label] + location: Span, + name: String, + }, + + #[error("positional argument after labeled")] + PositionalArgumentAfterLabeled { + #[label] + location: Span, + }, + + #[error("private type leaked")] + PrivateTypeLeak { + #[label] + location: Span, + leaked: Type, + }, + #[error("{name} is a reserved module name")] ReservedModuleName { name: String }, + #[error("unexpected labeled argument {label}")] + UnexpectedLabeledArg { + #[label] + location: Span, + label: String, + }, + #[error("unexpected type hole")] UnexpectedTypeHole { #[label] location: Span, }, + #[error("unknown labels")] + UnknownLabels { + unknown: Vec<(String, Span)>, + valid: Vec, + supplied: Vec, + }, + #[error("unknown module {name}")] UnknownModule { location: Span, @@ -85,54 +146,76 @@ pub enum Error { type_constructors: Vec, }, - #[error("")] + #[error("unknown module value {name}")] + UnknownModuleValue { + #[label] + location: Span, + name: String, + module_name: String, + value_constructors: Vec, + }, + + #[error("unknown type {name} in module {module_name}")] + UnknownModuleType { + #[label] + location: Span, + name: String, + module_name: String, + type_constructors: Vec, + }, + + #[error("unknown type {name}")] UnknownType { + #[label] location: Span, name: String, types: Vec, }, - #[error("")] - UnknownTypeConstructorType { + #[error("unknown variable {name}")] + UnknownVariable { + #[label] 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, + variables: Vec, }, #[error("")] CouldNotUnify { + #[label] location: Span, expected: Arc, given: Arc, situation: Option, + rigid_type_names: HashMap, }, #[error("")] - ExtraVarInAlternativePattern { location: Span, name: String }, + ExtraVarInAlternativePattern { + #[label] + location: Span, + name: String, + }, #[error("")] - MissingVarInAlternativePattern { location: Span, name: String }, + MissingVarInAlternativePattern { + #[label] + location: Span, + name: String, + }, #[error("")] - DuplicateVarInPattern { location: Span, name: String }, + DuplicateVarInPattern { + #[label] + location: Span, + name: String, + }, #[error("")] - RecursiveType { location: Span }, + RecursiveType { + #[label] + location: Span, + }, } impl Error { @@ -143,15 +226,53 @@ impl Error { expected, given, situation: note, + rigid_type_names, } => Error::CouldNotUnify { location, expected: given, given: expected, situation: note, + rigid_type_names, }, other => other, } } + + pub fn with_unify_error_rigid_names(mut self, new_names: &HashMap) -> Self { + match self { + Error::CouldNotUnify { + rigid_type_names: ref mut annotated_names, + .. + } => { + *annotated_names = new_names.clone(); + self + } + _ => self, + } + } + + pub fn with_unify_error_situation(self, situation: UnifyErrorSituation) -> Self { + match self { + Self::CouldNotUnify { + expected, + given, + location, + rigid_type_names, + .. + } => Self::CouldNotUnify { + expected, + given, + situation: Some(situation), + location, + rigid_type_names, + }, + other => other, + } + } + + pub fn return_annotation_mismatch(self) -> Self { + self.with_unify_error_situation(UnifyErrorSituation::ReturnAnnotationMismatch) + } } #[derive(Debug, PartialEq, Clone)] diff --git a/crates/lang/src/tipo/expr.rs b/crates/lang/src/tipo/expr.rs new file mode 100644 index 00000000..7c5bbadc --- /dev/null +++ b/crates/lang/src/tipo/expr.rs @@ -0,0 +1,597 @@ +use std::{collections::HashMap, sync::Arc}; + +use crate::{ + ast::{Annotation, ArgName, CallArg, Constant, Span, TypedArg, TypedConstant, UntypedConstant}, + builtins::list, + expr::{TypedExpr, UntypedExpr}, + tipo::fields::FieldMap, +}; + +use super::{ + environment::{assert_no_labeled_arguments, EntityKind, Environment}, + error::Error, + hydrator::Hydrator, + ModuleValueConstructor, Type, ValueConstructor, ValueConstructorVariant, +}; + +#[derive(Debug)] +pub(crate) struct ExprTyper<'a, 'b> { + pub(crate) environment: &'a mut Environment<'b>, + + // Type hydrator for creating types from annotations + pub(crate) hydrator: Hydrator, + + // We keep track of whether any ungeneralised functions have been used + // to determine whether it is safe to generalise this expression after + // it has been inferred. + pub(crate) ungeneralised_function_used: bool, +} + +impl<'a, 'b> ExprTyper<'a, 'b> { + fn get_field_map( + &mut self, + constructor: &TypedExpr, + location: Span, + ) -> Result, Error> { + let (module, name) = match constructor { + TypedExpr::ModuleSelect { + module_alias, + label, + .. + } => (Some(module_alias), label), + + TypedExpr::Var { name, .. } => (None, name), + + _ => return Ok(None), + }; + + Ok(self + .environment + .get_value_constructor(module, name, location)? + .field_map()) + } + + pub fn in_new_scope(&mut self, process_scope: impl FnOnce(&mut Self) -> T) -> T { + // Create new scope + let environment_reset_data = self.environment.open_new_scope(); + let hydrator_reset_data = self.hydrator.open_new_scope(); + + // Process the scope + let result = process_scope(self); + + // Close scope, discarding any scope local state + self.environment.close_scope(environment_reset_data); + self.hydrator.close_scope(hydrator_reset_data); + + result + } + + pub fn infer_fn_with_known_types( + &mut self, + args: Vec, + body: UntypedExpr, + return_type: Option>, + ) -> Result<(Vec, TypedExpr), Error> { + let (body_rigid_names, body_infer) = self.in_new_scope(|body_typer| { + for (arg, t) in args.iter().zip(args.iter().map(|arg| arg.tipo.clone())) { + match &arg.arg_name { + ArgName::Named { name, .. } | ArgName::NamedLabeled { name, .. } => { + body_typer.environment.insert_variable( + name.to_string(), + ValueConstructorVariant::LocalVariable { + location: arg.location, + }, + t, + ); + + body_typer.environment.init_usage( + name.to_string(), + EntityKind::Variable, + arg.location, + ); + } + ArgName::Discard { .. } | ArgName::LabeledDiscard { .. } => (), + }; + } + + (body_typer.hydrator.rigid_names(), body_typer.infer(body)) + }); + + let body = body_infer.map_err(|e| e.with_unify_error_rigid_names(&body_rigid_names))?; + + // Check that any return type is accurate. + if let Some(return_type) = return_type { + self.unify(return_type, body.tipo(), body.type_defining_location()) + .map_err(|e| { + e.return_annotation_mismatch() + .with_unify_error_rigid_names(&body_rigid_names) + })?; + } + + Ok((args, body)) + } + + /// Crawl the AST, annotating each node with the inferred type or + /// returning an error. + /// + pub fn infer(&mut self, expr: UntypedExpr) -> Result { + match expr { + UntypedExpr::Todo { + location, + label, + kind, + .. + } => Ok(self.infer_todo(location, kind, label)), + + UntypedExpr::Var { location, name, .. } => self.infer_var(name, location), + + UntypedExpr::Int { + location, value, .. + } => Ok(self.infer_int(value, location)), + + UntypedExpr::Sequence { + expressions, + location, + } => self.infer_seq(location, expressions), + + UntypedExpr::Tuple { + location, elems, .. + } => self.infer_tuple(elems, location), + + UntypedExpr::String { + location, value, .. + } => Ok(self.infer_string(value, location)), + + UntypedExpr::PipeLine { expressions } => self.infer_pipeline(expressions), + + UntypedExpr::Fn { + location, + is_capture, + arguments: args, + body, + return_annotation, + .. + } => self.infer_fn(args, &[], *body, is_capture, return_annotation, location), + + UntypedExpr::Assignment { + location, + pattern, + value, + kind, + annotation, + .. + } => self.infer_assignment(pattern, *value, kind, &annotation, location), + + UntypedExpr::Try { + location, + pattern, + value, + then, + annotation, + .. + } => self.infer_try(pattern, *value, *then, &annotation, location), + + UntypedExpr::Case { + location, + subjects, + clauses, + .. + } => self.infer_case(subjects, clauses, location), + + UntypedExpr::List { + location, + elements, + tail, + .. + } => self.infer_list(elements, tail, location), + + UntypedExpr::Call { + location, + fun, + arguments: args, + .. + } => self.infer_call(*fun, args, location), + + UntypedExpr::BinOp { + location, + name, + left, + right, + .. + } => self.infer_binop(name, *left, *right, location), + + UntypedExpr::FieldAccess { + location, + label, + container, + .. + } => self.infer_field_access(*container, label, location), + + UntypedExpr::TupleIndex { + location, + index, + tuple, + .. + } => self.infer_tuple_index(*tuple, index, location), + + UntypedExpr::BitString { location, segments } => { + self.infer_bit_string(segments, location) + } + + UntypedExpr::RecordUpdate { + location, + constructor, + spread, + arguments: args, + } => self.infer_record_update(*constructor, spread, args, location), + + UntypedExpr::Negate { location, value } => self.infer_negate(location, value), + } + } + + pub fn type_from_annotation(&mut self, annotation: &Annotation) -> Result, Error> { + self.hydrator + .type_from_annotation(annotation, self.environment) + } + + // TODO: extract the type annotation checking into a infer_module_const + // function that uses this function internally + pub fn infer_const( + &mut self, + annotation: &Option, + value: UntypedConstant, + ) -> Result { + let inferred = match value { + Constant::Int { + location, value, .. + } => Ok(Constant::Int { location, value }), + + Constant::String { + location, value, .. + } => Ok(Constant::String { location, value }), + + Constant::List { + elements, location, .. + } => self.infer_const_list(elements, location), + + Constant::ByteArray { location, bytes } => Ok(Constant::ByteArray { location, bytes }), + + Constant::Record { + module, + location, + name, + args, + // field_map, is always None here because untyped not yet unified + .. + } if args.is_empty() => { + // Register the module as having been used if it was imported + if let Some(ref module) = &module { + self.environment.unused_modules.remove(module); + } + + // Type check the record constructor + let constructor = self.infer_value_constructor(&module, &name, &location)?; + + let (tag, field_map) = match &constructor.variant { + ValueConstructorVariant::Record { + name, field_map, .. + } => (name.clone(), field_map.clone()), + + ValueConstructorVariant::ModuleFn { .. } + | ValueConstructorVariant::LocalVariable { .. } => { + return Err(Error::NonLocalClauseGuardVariable { location, name }) + } + + // TODO: remove this clone. Could use an rc instead + ValueConstructorVariant::ModuleConstant { literal, .. } => { + return Ok(literal.clone()) + } + }; + + Ok(Constant::Record { + module, + location, + name, + args: vec![], + tipo: constructor.tipo, + tag, + field_map, + }) + } + + Constant::Record { + module, + location, + name, + mut args, + // field_map, is always None here because untyped not yet unified + .. + } => { + // Register the module as having been used if it was imported + if let Some(ref module) = &module { + self.environment.unused_modules.remove(module); + } + + let constructor = self.infer_value_constructor(&module, &name, &location)?; + + let (tag, field_map) = match &constructor.variant { + ValueConstructorVariant::Record { + name, field_map, .. + } => (name.clone(), field_map.clone()), + + ValueConstructorVariant::ModuleFn { .. } + | ValueConstructorVariant::LocalVariable { .. } => { + return Err(Error::NonLocalClauseGuardVariable { location, name }) + } + + // TODO: remove this clone. Could be an rc instead + ValueConstructorVariant::ModuleConstant { literal, .. } => { + return Ok(literal.clone()) + } + }; + + // Pretty much all the other infer functions operate on UntypedExpr + // or TypedExpr rather than ClauseGuard. To make things easier we + // build the TypedExpr equivalent of the constructor and use that + // TODO: resvisit this. It is rather awkward at present how we + // have to convert to this other data structure. + let fun = match &module { + Some(module_name) => { + let tipo = Arc::clone(&constructor.tipo); + + let module_name = self + .environment + .imported_modules + .get(module_name) + .expect("Failed to find previously located module import") + .1 + .name; + + let module_value_constructor = ModuleValueConstructor::Record { + name: name.clone(), + field_map: field_map.clone(), + arity: args.len(), + tipo: Arc::clone(&tipo), + location: constructor.variant.location(), + }; + + TypedExpr::ModuleSelect { + label: name.clone(), + module_alias: module_name.clone(), + module_name, + tipo, + constructor: module_value_constructor, + location, + } + } + + None => TypedExpr::Var { + constructor, + location, + name: name.clone(), + }, + }; + + // This is basically the same code as do_infer_call_with_known_fun() + // except the args are typed with infer_clause_guard() here. + // This duplication is a bit awkward but it works! + // Potentially this could be improved later + match self.get_field_map(&fun, location)? { + // The fun has a field map so labelled arguments may be present and need to be reordered. + Some(field_map) => field_map.reorder(&mut args, location)?, + + // The fun has no field map and so we error if arguments have been labelled + None => assert_no_labeled_arguments(&args)?, + } + + let (mut args_types, return_type) = self.environment.match_fun_type( + fun.tipo(), + args.len(), + fun.location(), + location, + )?; + + let mut typed_args = Vec::new(); + + for (tipo, arg) in args_types.iter_mut().zip(args) { + let CallArg { + label, + value, + location, + } = arg; + + let value = self.infer_const(&None, value)?; + + self.unify(tipo.clone(), value.tipo(), value.location())?; + + typed_args.push(CallArg { + label, + value, + location, + }); + } + + Ok(Constant::Record { + module, + location, + name, + args: typed_args, + tipo: return_type, + tag, + field_map, + }) + } + Constant::Var { + location, + module, + name, + .. + } => { + // Register the module as having been used if it was imported + if let Some(ref module) = &module { + self.environment.unused_modules.remove(module); + } + + // Infer the type of this constant + let constructor = self.infer_value_constructor(&module, &name, &location)?; + + match constructor.variant { + ValueConstructorVariant::ModuleConstant { .. } + | ValueConstructorVariant::ModuleFn { .. } => Ok(Constant::Var { + location, + module, + name, + tipo: Arc::clone(&constructor.tipo), + constructor: Some(Box::from(constructor)), + }), + // constructor.variant cannot be a LocalVariable because module constants can + // only be defined at module scope. It also cannot be a Record because then + // this constant would have been parsed as a Constant::Record. Therefore this + // code is unreachable. + _ => unreachable!(), + } + } + }?; + + // Check type annotation is accurate. + if let Some(ann) = annotation { + let const_ann = self.type_from_annotation(ann)?; + + self.unify(const_ann, inferred.tipo(), inferred.location())?; + }; + + Ok(inferred) + } + + fn infer_const_list( + &mut self, + untyped_elements: Vec, + location: Span, + ) -> Result { + let tipo = self.new_unbound_var(); + + let mut elements = Vec::with_capacity(untyped_elements.len()); + + for element in untyped_elements { + let element = self.infer_const(&None, element)?; + + self.unify(tipo.clone(), element.tipo(), element.location())?; + + elements.push(element); + } + + Ok(Constant::List { + elements, + location, + tipo: list(tipo), + }) + } + + fn infer_value_constructor( + &mut self, + module: &Option, + name: &str, + location: &Span, + ) -> Result { + let constructor = match module { + // Look in the current scope for a binding with this name + None => { + let constructor = + self.environment + .get_variable(name) + .cloned() + .ok_or_else(|| Error::UnknownVariable { + location: *location, + name: name.to_string(), + variables: self.environment.local_value_names(), + })?; + + // Note whether we are using an ungeneralised function so that we can + // tell if it is safe to generalise this function after inference has + // completed. + if matches!( + &constructor.variant, + ValueConstructorVariant::ModuleFn { .. } + ) { + let is_ungeneralised = self.environment.ungeneralised_functions.contains(name); + + self.ungeneralised_function_used = + self.ungeneralised_function_used || is_ungeneralised; + } + + // Register the value as seen for detection of unused values + self.environment.increment_usage(name); + + constructor + } + + // Look in an imported module for a binding with this name + Some(module_name) => { + let (_, module) = &self + .environment + .imported_modules + .get(module_name) + .ok_or_else(|| Error::UnknownModule { + location: *location, + name: module_name.to_string(), + imported_modules: self + .environment + .imported_modules + .keys() + .map(|t| t.to_string()) + .collect(), + })?; + + module + .values + .get(name) + .cloned() + .ok_or_else(|| Error::UnknownModuleValue { + location: *location, + module_name: module_name.to_string(), + name: name.to_string(), + value_constructors: module.values.keys().map(|t| t.to_string()).collect(), + })? + } + }; + + let ValueConstructor { + public, + variant, + tipo, + } = constructor; + + // Instantiate generic variables into unbound variables for this usage + let tipo = self.instantiate(tipo, &mut HashMap::new()); + + Ok(ValueConstructor { + public, + variant, + tipo, + }) + } + + fn instantiate(&mut self, t: Arc, ids: &mut HashMap>) -> Arc { + self.environment.instantiate(t, ids, &self.hydrator) + } + + pub fn new(environment: &'a mut Environment<'b>) -> Self { + let mut hydrator = Hydrator::new(); + + hydrator.permit_holes(true); + + Self { + hydrator, + environment, + ungeneralised_function_used: false, + } + } + + pub fn new_unbound_var(&mut self) -> Arc { + self.environment.new_unbound_var() + } + + fn unify(&mut self, t1: Arc, t2: Arc, location: Span) -> Result<(), Error> { + self.environment.unify(t1, t2, location) + } +} diff --git a/crates/lang/src/tipo/fields.rs b/crates/lang/src/tipo/fields.rs new file mode 100644 index 00000000..eed8ac3f --- /dev/null +++ b/crates/lang/src/tipo/fields.rs @@ -0,0 +1,147 @@ +use std::collections::{HashMap, HashSet}; + +use itertools::Itertools; + +use super::error::Error; +use crate::ast::{CallArg, Span}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FieldMap { + pub arity: usize, + 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<(), Error> { + match self.fields.insert(label.clone(), index) { + Some(_) => Err(Error::DuplicateField { + label, + location: *location, + }), + None => Ok(()), + } + } + + pub fn into_option(self) -> Option { + if self.fields.is_empty() { + None + } else { + Some(self) + } + } + + /// Reorder an argument list so that labelled fields supplied out-of-order are + /// in the correct order. + pub fn reorder(&self, args: &mut [CallArg], location: Span) -> Result<(), Error> { + let mut labeled_arguments_given = false; + let mut seen_labels = std::collections::HashSet::new(); + let mut unknown_labels = Vec::new(); + + if self.arity as usize != args.len() { + return Err(Error::IncorrectArity { + labels: self.incorrect_arity_labels(args), + location, + expected: self.arity as usize, + given: args.len(), + }); + } + + for arg in args.iter() { + match &arg.label { + Some(_) => { + labeled_arguments_given = true; + } + + None => { + if labeled_arguments_given { + return Err(Error::PositionalArgumentAfterLabeled { + location: arg.location, + }); + } + } + } + } + + let mut i = 0; + while i < args.len() { + let label = &args.get(i).expect("Field indexing to get label").label; + + let (label, &location) = match label { + // A labelled argument, we may need to reposition it in the array vector + Some(l) => ( + l, + &args + .get(i) + .expect("Indexing in labelled field reordering") + .location, + ), + + // Not a labelled argument + None => { + i += 1; + continue; + } + }; + + let position = match self.fields.get(label) { + None => { + unknown_labels.push((label.clone(), location)); + + i += 1; + + continue; + } + + Some(&p) => p, + }; + + // If the argument is already in the right place + if position as usize == i { + seen_labels.insert(label.clone()); + + i += 1; + } else { + if seen_labels.contains(label) { + return Err(Error::DuplicateArgument { + location, + label: label.to_string(), + }); + } + + seen_labels.insert(label.clone()); + + args.swap(position as usize, i); + } + } + + if unknown_labels.is_empty() { + Ok(()) + } else { + let valid = self.fields.keys().map(|t| t.to_string()).collect(); + + Err(Error::UnknownLabels { + valid, + unknown: unknown_labels, + supplied: seen_labels.into_iter().collect(), + }) + } + } + + pub fn incorrect_arity_labels(&self, args: &[CallArg]) -> Vec { + let given: HashSet<_> = args.iter().filter_map(|arg| arg.label.as_ref()).collect(); + + self.fields + .keys() + .cloned() + .filter(|f| !given.contains(f)) + .sorted() + .collect() + } +} diff --git a/crates/lang/src/tipo/infer.rs b/crates/lang/src/tipo/infer.rs index e914d23c..f5338b4e 100644 --- a/crates/lang/src/tipo/infer.rs +++ b/crates/lang/src/tipo/infer.rs @@ -1,54 +1,423 @@ use std::collections::HashMap; use crate::{ - ast::{ModuleKind, TypedModule, UntypedModule}, + ast::{ + Definition, Layer, ModuleKind, RecordConstructor, RecordConstructorArg, TypedDefinition, + TypedModule, UntypedDefinition, UntypedModule, + }, + builtins::function, token::Token, IdGenerator, }; use super::{ - environment::Environment, + environment::{generalise, EntityKind, Environment}, error::{Error, Warning}, - Module, + expr::ExprTyper, + hydrator::Hydrator, + TypeInfo, ValueConstructor, ValueConstructorVariant, }; -pub fn module( - id_gen: &IdGenerator, - mut module: UntypedModule, - kind: ModuleKind, - package: &str, - 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); +impl UntypedModule { + pub fn infer( + mut self, + id_gen: &IdGenerator, + kind: ModuleKind, + package: &str, + modules: &HashMap, + warnings: &mut Vec, + ) -> Result { + let name = self.name.clone(); + let docs = std::mem::take(&mut self.docs); + let mut environment = Environment::new(id_gen.clone(), &name, modules, warnings); - validate_module_name(&name)?; + 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()); + let mut type_names = HashMap::with_capacity(self.definitions.len()); + let mut value_names = HashMap::with_capacity(self.definitions.len()); + let mut hydrators = HashMap::with_capacity(self.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 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 self.definitions() { + environment.register_import(def)?; + } + + // Register types so they can be used in constructors and functions + // earlier in the module. + for def in self.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 self.definitions() { + environment.register_values(def, &name, &mut hydrators, &mut value_names)?; + } + + // Infer the types of each definition in the module + // We first infer all the constants so they can be used in functions defined + // anywhere in the module. + let mut definitions = Vec::with_capacity(self.definitions.len()); + let mut consts = vec![]; + let mut not_consts = vec![]; + for def in self.into_definitions() { + match def { + Definition::ModuleConstant { .. } => consts.push(def), + + Definition::Fn { .. } + | Definition::TypeAlias { .. } + | Definition::DataType { .. } + | Definition::Use { .. } => not_consts.push(def), + } + } + + for def in consts.into_iter().chain(not_consts) { + let definition = infer_definition(def, &name, &mut hydrators, &mut environment)?; + + definitions.push(definition); + } + + // Generalise functions now that the entire module has been inferred + let definitions = definitions + .into_iter() + .map(|def| environment.generalise_definition(def, &name)) + .collect(); + + // Generate warnings for unused items + environment.convert_unused_to_warnings(); + + // Remove private and imported types and values to create the public interface + environment + .module_types + .retain(|_, info| info.public && info.module == name); + + environment.module_values.retain(|_, info| info.public); + + environment + .accessors + .retain(|_, accessors| accessors.public); + + // Ensure no exported values have private types in their type signature + for value in environment.module_values.values() { + if let Some(leaked) = value.tipo.find_private_type() { + return Err(Error::PrivateTypeLeak { + location: value.variant.location(), + leaked, + }); + } + } + + let Environment { + module_types: types, + module_types_constructors: types_constructors, + module_values: values, + accessors, + .. + } = environment; + + Ok(TypedModule { + docs, + name: name.clone(), + definitions, + kind, + type_info: TypeInfo { + name, + types, + types_constructors, + values, + accessors, + kind, + package: package.to_string(), + }, + }) } +} - // 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)?; +fn infer_definition( + def: UntypedDefinition, + module_name: &String, + hydrators: &mut HashMap, + environment: &mut Environment<'_>, +) -> Result { + match def { + Definition::Fn { + doc, + location, + name, + public, + arguments: args, + body, + return_annotation, + .. + } => { + let preregistered_fn = environment + .get_variable(&name) + .expect("Could not find preregistered type for function"); + + let field_map = preregistered_fn.field_map().cloned(); + + let preregistered_type = preregistered_fn.tipo.clone(); + + let (args_types, return_type) = preregistered_type + .function_types() + .expect("Preregistered type for fn was not a fn"); + + // Infer the type using the preregistered args + return types as a starting point + let (tipo, args, body, safe_to_generalise) = + environment.in_new_scope(|environment| { + let args = args + .into_iter() + .zip(&args_types) + .map(|(arg_name, tipo)| arg_name.set_type(tipo.clone())) + .collect(); + + let mut expr_typer = ExprTyper::new(environment); + + expr_typer.hydrator = hydrators + .remove(&name) + .expect("Could not find hydrator for fn"); + + let (args, body) = + expr_typer.infer_fn_with_known_types(args, body, Some(return_type))?; + + let args_types = args.iter().map(|a| a.tipo.clone()).collect(); + + let tipo = function(args_types, body.tipo()); + + let safe_to_generalise = !expr_typer.ungeneralised_function_used; + + Ok((tipo, args, body, safe_to_generalise)) + })?; + + // Assert that the inferred type matches the type of any recursive call + environment.unify(preregistered_type, tipo.clone(), location)?; + + // Generalise the function if safe to do so + let tipo = if safe_to_generalise { + environment.ungeneralised_functions.remove(&name); + + let tipo = generalise(tipo, 0); + + let module_fn = ValueConstructorVariant::ModuleFn { + name: name.clone(), + field_map, + module: module_name.to_owned(), + arity: args.len(), + location, + }; + + environment.insert_variable(name.clone(), module_fn, tipo.clone()); + + tipo + } else { + tipo + }; + + Ok(Definition::Fn { + doc, + location, + name, + public, + arguments: args, + return_annotation, + return_type: tipo + .return_type() + .expect("Could not find return type for fn"), + body, + }) + } + + Definition::TypeAlias { + doc, + location, + public, + alias, + parameters, + annotation, + .. + } => { + let tipo = environment + .get_type_constructor(&None, &alias, location) + .expect("Could not find existing type for type alias") + .tipo + .clone(); + + Ok(Definition::TypeAlias { + doc, + location, + public, + alias, + parameters, + annotation, + tipo, + }) + } + + Definition::DataType { + doc, + location, + public, + opaque, + name, + parameters, + constructors, + .. + } => { + let constructors = constructors + .into_iter() + .map( + |RecordConstructor { + location, + name, + arguments: args, + documentation, + sugar, + }| { + let preregistered_fn = environment + .get_variable(&name) + .expect("Could not find preregistered type for function"); + + let preregistered_type = preregistered_fn.tipo.clone(); + + let args = if let Some((args_types, _return_type)) = + preregistered_type.function_types() + { + args.into_iter() + .zip(&args_types) + .map( + |( + RecordConstructorArg { + label, + annotation, + location, + .. + }, + t, + )| { + RecordConstructorArg { + label, + annotation, + location, + tipo: t.clone(), + doc: None, + } + }, + ) + .collect() + } else { + vec![] + }; + + RecordConstructor { + location, + name, + arguments: args, + documentation, + sugar, + } + }, + ) + .collect(); + + let typed_parameters = environment + .get_type_constructor(&None, &name, location) + .expect("Could not find preregistered type constructor ") + .parameters + .clone(); + + Ok(Definition::DataType { + doc, + location, + public, + opaque, + name, + parameters, + constructors, + typed_parameters, + }) + } + + Definition::Use { + location, + module, + as_name, + mut unqualified, + .. + } => { + let name = module.join("/"); + + // Find imported module + let module_info = + environment + .importable_modules + .get(&name) + .ok_or_else(|| Error::UnknownModule { + location, + name, + imported_modules: environment.imported_modules.keys().cloned().collect(), + })?; + + // TODO: remove this most likely + // Record any imports that are types only as this information is + // needed to prevent types being imported in generated JavaScript + for import in unqualified.iter_mut() { + if environment.imported_types.contains(import.variable_name()) { + import.layer = Layer::Type; + } + } + + Ok(Definition::Use { + location, + module, + as_name, + unqualified, + package: module_info.package.clone(), + }) + } + + Definition::ModuleConstant { + doc, + location, + name, + annotation, + public, + value, + .. + } => { + let typed_expr = ExprTyper::new(environment).infer_const(&annotation, *value)?; + + let tipo = typed_expr.tipo(); + + let variant = ValueConstructor { + public, + variant: ValueConstructorVariant::ModuleConstant { + location, + literal: typed_expr.clone(), + module: module_name.to_owned(), + }, + tipo: tipo.clone(), + }; + + environment.insert_variable(name.clone(), variant.variant.clone(), tipo.clone()); + + environment.insert_module_value(&name, variant); + + if !public { + environment.init_usage(name.clone(), EntityKind::PrivateConstant, location); + } + + Ok(Definition::ModuleConstant { + doc, + location, + name, + annotation, + public, + value: Box::new(typed_expr), + tipo, + }) + } } - - // 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> { @@ -70,7 +439,7 @@ fn validate_module_name(name: &str) -> Result<(), Error> { Ok(()) } -pub fn str_to_keyword(word: &str) -> Option { +fn str_to_keyword(word: &str) -> Option { // Alphabetical keywords: match word { "as" => Some(Token::As),