diff --git a/crates/cli/src/error.rs b/crates/cli/src/error.rs index 24444774..35c3ec59 100644 --- a/crates/cli/src/error.rs +++ b/crates/cli/src/error.rs @@ -5,7 +5,7 @@ use std::{ }; use aiken_lang::{error::ParseError, tipo}; -use miette::{EyreContext, LabeledSpan, MietteHandlerOpts, RgbColors, SourceCode}; +use miette::{Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, RgbColors, SourceCode}; #[allow(dead_code)] #[derive(thiserror::Error)] @@ -27,7 +27,7 @@ pub enum Error { #[error("a list of errors")] List(Vec), - #[error("failed to parse")] + #[error("parsing")] Parse { path: PathBuf, @@ -37,10 +37,11 @@ pub enum Error { error: Box, }, - #[error("type checking failed")] + #[error("type checking")] Type { path: PathBuf, src: String, + #[source] error: tipo::error::Error, }, } @@ -52,6 +53,10 @@ impl Error { _ => 1, } } + + pub fn report(&self) { + eprintln!("Error: {:?}", self) + } } impl Debug for Error { @@ -73,7 +78,7 @@ impl Debug for Error { } } -impl miette::Diagnostic for Error { +impl Diagnostic for Error { fn code<'a>(&'a self) -> Option> { match self { Error::DuplicateModule { .. } => Some(Box::new("aiken::module::duplicate")), @@ -99,7 +104,7 @@ impl miette::Diagnostic for Error { ))), Error::List(_) => None, Error::Parse { error, .. } => error.kind.help(), - Error::Type { .. } => None, + Error::Type { error, .. } => error.help(), } } @@ -125,3 +130,62 @@ impl miette::Diagnostic for Error { } } } + +#[derive(PartialEq, thiserror::Error)] +pub enum Warning { + #[error("type checking")] + Type { + path: PathBuf, + src: String, + #[source] + warning: tipo::error::Warning, + }, +} + +impl Diagnostic for Warning { + fn source_code(&self) -> Option<&dyn SourceCode> { + match self { + Warning::Type { src, .. } => Some(src), + } + } + fn labels(&self) -> Option + '_>> { + match self { + Warning::Type { warning, .. } => warning.labels(), + } + } + + fn code<'a>(&'a self) -> Option> { + match self { + Warning::Type { .. } => Some(Box::new("aiken::typecheck")), + } + } +} + +impl Warning { + pub fn from_type_warning(warning: tipo::error::Warning, path: PathBuf, src: String) -> Warning { + Warning::Type { path, warning, src } + } + + pub fn report(&self) { + eprintln!("Warning: {:?}", self) + } +} + +impl Debug for Warning { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let miette_handler = MietteHandlerOpts::new() + // For better support of terminal themes use the ANSI coloring + .rgb_colors(RgbColors::Never) + // If ansi support is disabled in the config disable the eye-candy + .color(true) + .unicode(true) + .terminal_links(true) + .build(); + + // Ignore error to prevent format! panics. This can happen if span points at some + // inaccessible location, for example by calling `report_error()` with wrong working set. + let _ = miette_handler.debug(self, f); + + Ok(()) + } +} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index e639f9a6..ddd3d8a1 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -52,7 +52,7 @@ fn main() -> miette::Result<()> { let build_result = project.build(); for warning in project.warnings { - eprintln!("Warning: {:?}", warning) + warning.report() } if let Err(err) = build_result { @@ -65,7 +65,7 @@ fn main() -> miette::Result<()> { rest => eprintln!("Error: {:?}", rest), } - miette::bail!("failed: {} errors", err.total()); + // miette::bail!("failed: {} errors", err.total()); }; } diff --git a/crates/cli/src/project.rs b/crates/cli/src/project.rs index 7d6c72b8..76a37ede 100644 --- a/crates/cli/src/project.rs +++ b/crates/cli/src/project.rs @@ -4,16 +4,11 @@ use std::{ path::{Path, PathBuf}, }; -use aiken_lang::{ - ast::ModuleKind, - builtins, - tipo::{self, TypeInfo}, - IdGenerator, -}; +use aiken_lang::{ast::ModuleKind, builtins, tipo::TypeInfo, IdGenerator}; use crate::{ config::Config, - error::Error, + error::{Error, Warning}, module::{CheckedModule, ParsedModule, ParsedModules}, }; @@ -25,21 +20,6 @@ pub struct Source { pub kind: ModuleKind, } -#[derive(Debug, PartialEq)] -pub enum Warning { - Type { - path: PathBuf, - src: String, - warning: tipo::error::Warning, - }, -} - -impl Warning { - pub fn from_type_warning(warning: tipo::error::Warning, path: PathBuf, src: String) -> Warning { - Warning::Type { path, warning, src } - } -} - pub struct Project { config: Config, defined_modules: HashMap, diff --git a/crates/lang/src/ast.rs b/crates/lang/src/ast.rs index 2769a127..a5c27d5c 100644 --- a/crates/lang/src/ast.rs +++ b/crates/lang/src/ast.rs @@ -3,7 +3,7 @@ use std::{fmt, ops::Range, sync::Arc}; use internment::Intern; use crate::{ - builtins, + builtins::{self, bool}, expr::{TypedExpr, UntypedExpr}, tipo::{fields::FieldMap, PatternConstructor, Type, TypeInfo, ValueConstructor}, }; @@ -122,6 +122,12 @@ pub enum Definition { }, } +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct DefinitionLocation<'module> { + pub module: Option<&'module str>, + pub span: Span, +} + pub type TypedConstant = Constant, String>; pub type UntypedConstant = Constant<(), ()>; @@ -200,6 +206,8 @@ impl Constant { } } +pub type TypedCallArg = CallArg; + #[derive(Debug, Clone, PartialEq, Eq)] pub struct CallArg { pub label: Option, @@ -453,11 +461,6 @@ pub enum Pattern { value: String, }, - Float { - location: Span, - value: String, - }, - String { location: Span, value: String, @@ -509,11 +512,34 @@ pub enum Pattern { with_spread: bool, tipo: Type, }, + // Tuple { + // location: Span, + // elems: Vec, + // }, +} - Tuple { - location: Span, - elems: Vec, - }, +impl Pattern { + pub fn location(&self) -> Span { + match self { + Pattern::Assign { pattern, .. } => pattern.location(), + Pattern::Int { location, .. } + | Pattern::Var { location, .. } + | Pattern::VarUsage { location, .. } + | Pattern::List { location, .. } + | Pattern::Discard { location, .. } + | Pattern::String { location, .. } + // | Pattern::Tuple { location, .. } + // | Pattern::Concatenate { location, .. } + | Pattern::Constructor { location, .. } => *location, + } + } + + /// Returns `true` if the pattern is [`Discard`]. + /// + /// [`Discard`]: Pattern::Discard + pub fn is_discard(&self) -> bool { + matches!(self, Self::Discard { .. }) + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -528,7 +554,6 @@ pub type UntypedMultiPattern = MultiPattern<(), ()>; pub type TypedMultiPattern = MultiPattern>; pub type TypedClause = Clause, String>; - pub type UntypedClause = Clause; #[derive(Debug, Clone, PartialEq)] @@ -540,6 +565,23 @@ pub struct Clause { pub then: Expr, } +impl TypedClause { + pub fn location(&self) -> Span { + Span { + src: SrcId::empty(), + start: self + .pattern + .get(0) + .map(|p| p.location().start) + .unwrap_or_default(), + end: self.then.location().end, + } + } +} + +pub type UntypedClauseGuard = ClauseGuard<(), ()>; +pub type TypedClauseGuard = ClauseGuard, String>; + #[derive(Debug, Clone, PartialEq)] pub enum ClauseGuard { Equals { @@ -596,16 +638,52 @@ pub enum ClauseGuard { name: String, }, - TupleIndex { - location: Span, - index: u64, - tipo: Type, - tuple: Box, - }, - + // TupleIndex { + // location: Span, + // index: u64, + // tipo: Type, + // tuple: Box, + // }, Constant(Constant), } +impl ClauseGuard { + pub fn location(&self) -> Span { + match self { + ClauseGuard::Constant(constant) => constant.location(), + ClauseGuard::Or { location, .. } + | ClauseGuard::And { location, .. } + | ClauseGuard::Var { location, .. } + // | ClauseGuard::TupleIndex { location, .. } + | ClauseGuard::Equals { location, .. } + | ClauseGuard::NotEquals { location, .. } + | ClauseGuard::GtInt { location, .. } + | ClauseGuard::GtEqInt { location, .. } + | ClauseGuard::LtInt { location, .. } + | ClauseGuard::LtEqInt { location, .. } => *location, + } + } +} + +impl TypedClauseGuard { + pub fn tipo(&self) -> Arc { + match self { + ClauseGuard::Var { tipo, .. } => tipo.clone(), + // ClauseGuard::TupleIndex { type_, .. } => type_.clone(), + ClauseGuard::Constant(constant) => constant.tipo(), + + ClauseGuard::Or { .. } + | ClauseGuard::And { .. } + | ClauseGuard::Equals { .. } + | ClauseGuard::NotEquals { .. } + | ClauseGuard::GtInt { .. } + | ClauseGuard::GtEqInt { .. } + | ClauseGuard::LtInt { .. } + | ClauseGuard::LtEqInt { .. } => bool(), + } + } +} + pub type TypedIfBranch = IfBranch; pub type UntypedIfBranch = IfBranch; @@ -616,7 +694,7 @@ pub struct IfBranch { pub location: Span, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TypedRecordUpdateArg { pub label: String, pub location: Span, diff --git a/crates/lang/src/expr.rs b/crates/lang/src/expr.rs index 99b7343b..c5ab2a45 100644 --- a/crates/lang/src/expr.rs +++ b/crates/lang/src/expr.rs @@ -4,14 +4,14 @@ use vec1::Vec1; use crate::{ ast::{ - Annotation, Arg, AssignmentKind, BinOp, CallArg, Clause, IfBranch, Pattern, - RecordUpdateSpread, Span, TodoKind, TypedRecordUpdateArg, UntypedRecordUpdateArg, + Annotation, Arg, AssignmentKind, BinOp, CallArg, Clause, DefinitionLocation, IfBranch, + Pattern, RecordUpdateSpread, Span, TodoKind, TypedRecordUpdateArg, UntypedRecordUpdateArg, }, builtins::{bool, nil}, tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor}, }; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum TypedExpr { Int { location: Span, @@ -130,19 +130,18 @@ pub enum TypedExpr { constructor: ModuleValueConstructor, }, - Tuple { - location: Span, - tipo: Arc, - elems: Vec, - }, - - TupleIndex { - location: Span, - tipo: Arc, - index: u64, - tuple: Box, - }, + // Tuple { + // location: Span, + // tipo: Arc, + // elems: Vec, + // }, + // TupleIndex { + // location: Span, + // tipo: Arc, + // index: u64, + // tuple: Box, + // }, Todo { location: Span, label: Option, @@ -176,10 +175,10 @@ impl TypedExpr { | Self::Call { tipo, .. } | Self::If { tipo, .. } | Self::BinOp { tipo, .. } - | Self::Tuple { tipo, .. } + // | Self::Tuple { tipo, .. } | Self::String { tipo, .. } | Self::ByteArray { tipo, .. } - | Self::TupleIndex { tipo, .. } + // | Self::TupleIndex { tipo, .. } | Self::Assignment { tipo, .. } | Self::ModuleSelect { tipo, .. } | Self::RecordAccess { tipo, .. } @@ -190,6 +189,62 @@ impl TypedExpr { } } + pub fn is_literal(&self) -> bool { + matches!( + self, + Self::Int { .. } + | Self::List { .. } + // | Self::Tuple { .. } + | Self::String { .. } + | Self::ByteArray { .. } + ) + } + + /// Returns `true` if the typed expr is [`Assignment`]. + pub fn is_assignment(&self) -> bool { + matches!(self, Self::Assignment { .. }) + } + + pub fn definition_location(&self) -> Option> { + match self { + TypedExpr::Fn { .. } + | TypedExpr::Int { .. } + | TypedExpr::Try { .. } + | TypedExpr::List { .. } + | TypedExpr::Call { .. } + | TypedExpr::When { .. } + | TypedExpr::Todo { .. } + | TypedExpr::BinOp { .. } + // | TypedExpr::Tuple { .. } + | TypedExpr::Negate { .. } + | TypedExpr::String { .. } + | TypedExpr::Sequence { .. } + | TypedExpr::Pipeline { .. } + | TypedExpr::ByteArray { .. } + | TypedExpr::Assignment { .. } + // | TypedExpr::TupleIndex { .. } + | TypedExpr::RecordAccess { .. } => None, + | TypedExpr::If { .. } => None, + + // TODO: test + // TODO: definition + TypedExpr::RecordUpdate { .. } => None, + + // TODO: test + TypedExpr::ModuleSelect { + module_name, + constructor, + .. + } => Some(DefinitionLocation { + module: Some(module_name.as_str()), + span: constructor.location(), + }), + + // TODO: test + TypedExpr::Var { constructor, .. } => Some(constructor.definition_location()), + } + } + pub fn type_defining_location(&self) -> Span { match self { Self::Fn { location, .. } @@ -201,22 +256,20 @@ impl TypedExpr { | Self::Call { location, .. } | Self::List { location, .. } | Self::BinOp { location, .. } - | Self::Tuple { location, .. } + // | Self::Tuple { location, .. } | Self::String { location, .. } | Self::Negate { location, .. } | Self::Pipeline { location, .. } | Self::ByteArray { location, .. } | Self::Assignment { location, .. } - | Self::TupleIndex { 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 { @@ -242,14 +295,14 @@ impl TypedExpr { | Self::If { location, .. } | Self::List { location, .. } | Self::BinOp { location, .. } - | Self::Tuple { 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::TupleIndex { location, .. } | Self::ModuleSelect { location, .. } | Self::RecordAccess { location, .. } | Self::RecordUpdate { location, .. } => *location, @@ -349,17 +402,15 @@ pub enum UntypedExpr { container: Box, }, - Tuple { - location: Span, - elems: Vec, - }, - - TupleIndex { - location: Span, - index: u64, - tuple: Box, - }, - + // Tuple { + // location: Span, + // elems: Vec, + // }, + // TupleIndex { + // location: Span, + // index: u64, + // tuple: Box, + // }, Todo { kind: TodoKind, location: Span, @@ -442,10 +493,10 @@ impl UntypedExpr { | Self::List { location, .. } | Self::ByteArray { location, .. } | Self::BinOp { location, .. } - | Self::Tuple { location, .. } + // | Self::Tuple { location, .. } | Self::String { location, .. } | Self::Assignment { location, .. } - | Self::TupleIndex { location, .. } + // | Self::TupleIndex { location, .. } | Self::FieldAccess { location, .. } | Self::RecordUpdate { location, .. } | Self::Negate { location, .. } diff --git a/crates/lang/src/tipo.rs b/crates/lang/src/tipo.rs index 11fbb9aa..84f5f584 100644 --- a/crates/lang/src/tipo.rs +++ b/crates/lang/src/tipo.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, collections::HashMap, ops::Deref, sync::Arc}; use crate::{ - ast::{Constant, ModuleKind, Span, TypedConstant}, + ast::{Constant, DefinitionLocation, ModuleKind, Span, TypedConstant}, tipo::fields::FieldMap, }; @@ -13,6 +13,8 @@ mod expr; pub mod fields; mod hydrator; mod infer; +mod pattern; +mod pipe; #[derive(Debug, Clone, PartialEq)] pub enum Type { @@ -125,7 +127,7 @@ impl Type { pub fn get_app_args( &self, public: bool, - module: &String, + module: &str, name: &str, arity: usize, environment: &mut Environment<'_>, @@ -294,6 +296,30 @@ impl ValueConstructor { _ => None, } } + + pub fn is_local_variable(&self) -> bool { + self.variant.is_local_variable() + } + + pub fn definition_location(&self) -> DefinitionLocation<'_> { + match &self.variant { + ValueConstructorVariant::Record { + module, location, .. + } + | ValueConstructorVariant::ModuleConstant { + location, module, .. + } => DefinitionLocation { + module: Some(module.as_str()), + span: *location, + }, + + ValueConstructorVariant::ModuleFn { location, .. } + | ValueConstructorVariant::LocalVariable { location } => DefinitionLocation { + module: None, + span: *location, + }, + } + } } #[derive(Debug, Clone, PartialEq)] @@ -329,6 +355,54 @@ pub enum ValueConstructorVariant { } impl ValueConstructorVariant { + fn to_module_value_constructor( + &self, + tipo: Arc, + module_name: &str, + function_name: &str, + ) -> ModuleValueConstructor { + match self { + Self::Record { + name, + arity, + field_map, + location, + .. + } => ModuleValueConstructor::Record { + name: name.clone(), + field_map: field_map.clone(), + arity: *arity, + tipo, + location: *location, + }, + + // TODO: remove this clone with an rc clone + Self::ModuleConstant { + literal, location, .. + } => ModuleValueConstructor::Constant { + literal: literal.clone(), + location: *location, + }, + + Self::LocalVariable { location, .. } => ModuleValueConstructor::Fn { + name: function_name.to_string(), + module: module_name.to_string(), + location: *location, + }, + + Self::ModuleFn { + name, + module, + location, + .. + } => ModuleValueConstructor::Fn { + name: name.clone(), + module: module.clone(), + location: *location, + }, + } + } + pub fn location(&self) -> Span { match self { ValueConstructorVariant::LocalVariable { location } @@ -337,6 +411,11 @@ impl ValueConstructorVariant { | ValueConstructorVariant::Record { location, .. } => *location, } } + + /// Returns `true` if the variant is [`LocalVariable`]. + pub fn is_local_variable(&self) -> bool { + matches!(self, Self::LocalVariable { .. }) + } } #[derive(Debug, Clone)] @@ -374,7 +453,7 @@ pub struct RecordAccessor { pub tipo: Arc, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum PatternConstructor { Record { name: String, @@ -382,7 +461,7 @@ pub enum PatternConstructor { }, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ModuleValueConstructor { Record { name: String, @@ -394,6 +473,20 @@ pub enum ModuleValueConstructor { Fn { location: Span, + /// The name of the module and the function + /// Typically this will be the module that this constructor belongs to + /// and the name that was used for the function. However it could also + /// point to some other module and function when this is an `external fn`. + /// + /// This function has module "themodule" and name "wibble" + /// pub fn wibble() { Nil } + /// + /// This function has module "other" and name "whoop" + /// pub external fn wibble() -> Nil = + /// "other" "whoop" + /// + module: String, + name: String, }, Constant { @@ -401,3 +494,13 @@ pub enum ModuleValueConstructor { location: Span, }, } + +impl ModuleValueConstructor { + pub fn location(&self) -> Span { + match self { + ModuleValueConstructor::Fn { location, .. } + | ModuleValueConstructor::Record { location, .. } + | ModuleValueConstructor::Constant { location, .. } => *location, + } + } +} diff --git a/crates/lang/src/tipo/environment.rs b/crates/lang/src/tipo/environment.rs index 0837bedf..184a7174 100644 --- a/crates/lang/src/tipo/environment.rs +++ b/crates/lang/src/tipo/environment.rs @@ -4,10 +4,12 @@ use std::{ sync::Arc, }; +use itertools::Itertools; + use crate::{ ast::{ - Annotation, ArgName, CallArg, Definition, RecordConstructor, RecordConstructorArg, Span, - TypedDefinition, UnqualifiedImport, UntypedDefinition, PIPE_VARIABLE, + Annotation, ArgName, CallArg, Definition, Pattern, RecordConstructor, RecordConstructorArg, + Span, TypedDefinition, UnqualifiedImport, UntypedDefinition, PIPE_VARIABLE, }, builtins::{function, generic_var, unbound_var}, tipo::fields::FieldMap, @@ -17,8 +19,8 @@ use crate::{ use super::{ error::{Error, Warning}, hydrator::Hydrator, - AccessorsMap, RecordAccessor, Type, TypeConstructor, TypeInfo, TypeVar, ValueConstructor, - ValueConstructorVariant, + AccessorsMap, PatternConstructor, RecordAccessor, Type, TypeConstructor, TypeInfo, TypeVar, + ValueConstructor, ValueConstructorVariant, }; #[derive(Debug)] @@ -1213,6 +1215,114 @@ impl<'a> Environment<'a> { }), } } + + /// Checks that the given patterns are exhaustive for given type. + /// Currently only performs exhaustiveness checking for custom types, + /// only at the top level (without recursing into constructor arguments). + pub fn check_exhaustiveness( + &mut self, + patterns: Vec>>, + value_typ: Arc, + location: Span, + ) -> Result<(), Vec> { + match &*value_typ { + Type::App { + name: type_name, + module, + .. + } => { + let m = if module.is_empty() || module == self.current_module { + None + } else { + Some(module.clone()) + }; + + if let Ok(constructors) = self.get_constructors_for_type(&m, type_name, location) { + let mut unmatched_constructors: HashSet = + constructors.iter().cloned().collect(); + + for p in &patterns { + // ignore Assign patterns + let mut pattern = p; + while let Pattern::Assign { + pattern: assign_pattern, + .. + } = pattern + { + pattern = assign_pattern; + } + + match pattern { + // If the pattern is a Discard or Var, all constructors are covered by it + Pattern::Discard { .. } => return Ok(()), + Pattern::Var { .. } => return Ok(()), + // If the pattern is a constructor, remove it from unmatched patterns + Pattern::Constructor { + constructor: PatternConstructor::Record { name, .. }, + .. + } => { + unmatched_constructors.remove(name); + } + _ => return Ok(()), + } + } + + if !unmatched_constructors.is_empty() { + return Err(unmatched_constructors.into_iter().sorted().collect()); + } + } + Ok(()) + } + _ => Ok(()), + } + } + + /// Lookup constructors for type in the current scope. + /// + pub fn get_constructors_for_type( + &mut self, + full_module_name: &Option, + name: &str, + location: Span, + ) -> Result<&Vec, Error> { + match full_module_name { + None => self + .module_types_constructors + .get(name) + .ok_or_else(|| Error::UnknownType { + name: name.to_string(), + types: self.module_types.keys().map(|t| t.to_string()).collect(), + location, + }), + + Some(m) => { + let module = + self.importable_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_constructors + .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(), + }) + } + } + } } /// For Keeping track of entity usages and knowing which error to display. @@ -1359,6 +1469,15 @@ pub(super) fn assert_no_labeled_arguments(args: &[CallArg]) -> Result<(), Ok(()) } +pub(super) fn collapse_links(t: Arc) -> Arc { + if let Type::Var { tipo } = t.deref() { + if let TypeVar::Link { tipo } = tipo.borrow().deref() { + return tipo.clone(); + } + } + t +} + /// Returns the fields that have the same label and type across all variants of /// the given type. fn get_compatible_record_fields( diff --git a/crates/lang/src/tipo/error.rs b/crates/lang/src/tipo/error.rs index c7b7f7e5..5ff77280 100644 --- a/crates/lang/src/tipo/error.rs +++ b/crates/lang/src/tipo/error.rs @@ -69,6 +69,14 @@ pub enum Error { labels: Vec, }, + #[error("incorrect number of clause patterns expected {expected} but given {given}")] + IncorrectNumClausePatterns { + #[label] + location: Span, + expected: usize, + given: usize, + }, + #[error("{name} has incorrect type arity expected {expected} but given {given}")] IncorrectTypeArity { location: Span, @@ -77,6 +85,13 @@ pub enum Error { given: usize, }, + #[error("non-exhaustive pattern match")] + NotExhaustivePatternMatch { + #[label] + location: Span, + unmatched: Vec, + }, + #[error("not a function")] NotFn { #[label] @@ -107,6 +122,18 @@ pub enum Error { leaked: Type, }, + #[error("record access unknown type")] + RecordAccessUnknownType { + #[label] + location: Span, + }, + + #[error("record update invalid constructor")] + RecordUpdateInvalidConstructor { + #[label] + location: Span, + }, + #[error("{name} is a reserved module name")] ReservedModuleName { name: String }, @@ -132,6 +159,7 @@ pub enum Error { #[error("unknown module {name}")] UnknownModule { + #[label] location: Span, name: String, imported_modules: Vec, @@ -164,6 +192,16 @@ pub enum Error { type_constructors: Vec, }, + #[error("unknown record field {label}")] + UnknownRecordField { + #[label] + location: Span, + typ: Arc, + label: String, + fields: Vec, + situation: Option, + }, + #[error("unknown type {name}")] UnknownType { #[label] @@ -180,6 +218,19 @@ pub enum Error { variables: Vec, }, + #[error("unnecessary spread operator")] + UnnecessarySpreadOperator { + #[label] + location: Span, + arity: usize, + }, + + #[error("cannot update a type with multiple constructors")] + UpdateMultiConstructorType { + #[label] + location: Span, + }, + #[error("")] CouldNotUnify { #[label] @@ -219,6 +270,20 @@ pub enum Error { } impl Error { + pub fn call_situation(mut self) -> Self { + if let Error::UnknownRecordField { + ref mut situation, .. + } = self + { + *situation = Some(UnknownRecordFieldSituation::FunctionCall); + } + self + } + + pub fn case_clause_mismatch(self) -> Self { + self.with_unify_error_situation(UnifyErrorSituation::CaseClauseMismatch) + } + pub fn flip_unify(self) -> Error { match self { Error::CouldNotUnify { @@ -238,6 +303,22 @@ impl Error { } } + pub fn inconsistent_try(self, return_value_is_result: bool) -> Self { + self.with_unify_error_situation(if return_value_is_result { + UnifyErrorSituation::TryErrorMismatch + } else { + UnifyErrorSituation::TryReturnResult + }) + } + + pub fn operator_situation(self, binop: BinOp) -> Self { + self.with_unify_error_situation(UnifyErrorSituation::Operator(binop)) + } + + pub fn return_annotation_mismatch(self) -> Self { + self.with_unify_error_situation(UnifyErrorSituation::ReturnAnnotationMismatch) + } + pub fn with_unify_error_rigid_names(mut self, new_names: &HashMap) -> Self { match self { Error::CouldNotUnify { @@ -269,69 +350,89 @@ impl Error { other => other, } } - - pub fn return_annotation_mismatch(self) -> Self { - self.with_unify_error_situation(UnifyErrorSituation::ReturnAnnotationMismatch) - } } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, thiserror::Error, Diagnostic)] pub enum Warning { + #[error("todo")] Todo { kind: TodoKind, + #[label] location: Span, - typ: Arc, + tipo: Arc, }, + #[error("implicitly discarded result")] ImplicitlyDiscardedResult { + #[label] location: Span, }, + #[error("unused literal")] UnusedLiteral { + #[label] location: Span, }, + #[error("record update with no fields")] NoFieldsRecordUpdate { + #[label] location: Span, }, + #[error("record update using all fields")] AllFieldsRecordUpdate { + #[label] location: Span, }, + #[error("unused type {name}")] UnusedType { + #[label] location: Span, imported: bool, name: String, }, + #[error("unused constructor {name}")] UnusedConstructor { + #[label] location: Span, imported: bool, name: String, }, + #[error("unused imported value {name}")] UnusedImportedValue { + #[label] location: Span, name: String, }, + #[error("unused imported module {name}")] UnusedImportedModule { + #[label] location: Span, name: String, }, + #[error("unused private module constant {name}")] UnusedPrivateModuleConstant { + #[label] location: Span, name: String, }, + #[error("unused private function {name}")] UnusedPrivateFunction { + #[label] location: Span, name: String, }, + #[error("unused variable {name}")] UnusedVariable { + #[label] location: Span, name: String, }, @@ -357,3 +458,9 @@ pub enum UnifyErrorSituation { /// The final value of a try expression was not a Result. TryReturnResult, } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UnknownRecordFieldSituation { + /// This unknown record field is being called as a function. i.e. `record.field()` + FunctionCall, +} diff --git a/crates/lang/src/tipo/expr.rs b/crates/lang/src/tipo/expr.rs index 7c5bbadc..d05a8e2d 100644 --- a/crates/lang/src/tipo/expr.rs +++ b/crates/lang/src/tipo/expr.rs @@ -1,17 +1,28 @@ use std::{collections::HashMap, sync::Arc}; +use vec1::Vec1; + use crate::{ - ast::{Annotation, ArgName, CallArg, Constant, Span, TypedArg, TypedConstant, UntypedConstant}, - builtins::list, + ast::{ + Annotation, Arg, ArgName, AssignmentKind, BinOp, CallArg, Clause, ClauseGuard, Constant, + RecordUpdateSpread, Span, SrcId, TodoKind, TypedArg, TypedCallArg, TypedClause, + TypedClauseGuard, TypedConstant, TypedMultiPattern, TypedRecordUpdateArg, UntypedArg, + UntypedClause, UntypedClauseGuard, UntypedConstant, UntypedMultiPattern, UntypedPattern, + UntypedRecordUpdateArg, + }, + builtins::{bool, byte_array, function, int, list, result, string}, expr::{TypedExpr, UntypedExpr}, tipo::fields::FieldMap, }; use super::{ - environment::{assert_no_labeled_arguments, EntityKind, Environment}, - error::Error, + environment::{assert_no_labeled_arguments, collapse_links, EntityKind, Environment}, + error::{Error, Warning}, hydrator::Hydrator, - ModuleValueConstructor, Type, ValueConstructor, ValueConstructorVariant, + pattern::PatternTyper, + pipe::PipeTyper, + ModuleValueConstructor, PatternConstructor, RecordAccessor, Type, ValueConstructor, + ValueConstructorVariant, }; #[derive(Debug)] @@ -28,6 +39,152 @@ pub(crate) struct ExprTyper<'a, 'b> { } impl<'a, 'b> ExprTyper<'a, 'b> { + fn check_when_exhaustiveness( + &mut self, + subjects_count: usize, + subjects: &[Arc], + typed_clauses: &[Clause, String>], + location: Span, + ) -> Result<(), Vec> { + // Because exhaustiveness checking in presence of multiple subjects is similar + // to full exhaustiveness checking of tuples or other nested record patterns, + // and we currently only do only limited exhaustiveness checking of custom types + // at the top level of patterns, only consider case expressions with one subject. + if subjects_count != 1 { + return Ok(()); + } + + let subject_type = subjects + .get(0) + .expect("Asserted there's one case subject but found none"); + + let value_typ = collapse_links(subject_type.clone()); + + // Currently guards in exhaustiveness checking are assumed that they can fail, + // so we go through all clauses and pluck out only the patterns + // for clauses that don't have guards. + let mut patterns = Vec::new(); + for clause in typed_clauses { + if let Clause { guard: None, .. } = clause { + // clause.pattern is a list of patterns for all subjects + if let Some(pattern) = clause.pattern.get(0) { + patterns.push(pattern.clone()); + } + + // A clause can be built with alternative patterns as well, e.g. `Audio(_) | Text(_) ->`. + // We're interested in all patterns so we build a flattened list. + for alternative_pattern in &clause.alternative_patterns { + // clause.alternative_pattern is a list of patterns for all subjects + if let Some(pattern) = alternative_pattern.get(0) { + patterns.push(pattern.clone()); + } + } + } + } + + self.environment + .check_exhaustiveness(patterns, value_typ, location) + } + + pub fn do_infer_call( + &mut self, + fun: UntypedExpr, + args: Vec>, + location: Span, + ) -> Result<(TypedExpr, Vec, Arc), Error> { + let fun = self.infer(fun)?; + + let (fun, args, typ) = self.do_infer_call_with_known_fun(fun, args, location)?; + + Ok((fun, args, typ)) + } + + pub fn do_infer_call_with_known_fun( + &mut self, + fun: TypedExpr, + mut args: Vec>, + location: Span, + ) -> Result<(TypedExpr, Vec, Arc), Error> { + // Check to see if the function accepts labelled arguments + 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)?, + } + + // Extract the type of the fun, ensuring it actually is a function + let (mut args_types, return_type) = + self.environment + .match_fun_type(fun.tipo(), args.len(), fun.location(), location)?; + + let mut arguments = Vec::new(); + + for (tipo, arg) in args_types.iter_mut().zip(args) { + let CallArg { + label, + value, + location, + } = arg; + + let value = self.infer_call_argument(value, tipo.clone())?; + + arguments.push(CallArg { + label, + value, + location, + }); + } + + Ok((fun, arguments, return_type)) + } + + pub fn do_infer_fn( + &mut self, + args: Vec, + expected_args: &[Arc], + body: UntypedExpr, + return_annotation: &Option, + ) -> Result<(Vec, TypedExpr), Error> { + // Construct an initial type for each argument of the function- either an unbound + // type variable or a type provided by an annotation. + + let mut arguments = Vec::new(); + + for (i, arg) in args.into_iter().enumerate() { + let arg = self.infer_arg(arg, expected_args.get(i).cloned())?; + + arguments.push(arg); + } + + let return_type = match return_annotation { + Some(ann) => Some(self.type_from_annotation(ann)?), + None => None, + }; + + self.infer_fn_with_known_types(arguments, body, return_type) + } + + /// Emit a warning if the given expressions should not be discarded. + /// e.g. because it's a literal (why was it made in the first place?) + /// e.g. because it's of the `Result` type (errors should be handled) + fn expression_discarded(&mut self, discarded: &TypedExpr) { + if discarded.is_literal() { + self.environment.warnings.push(Warning::UnusedLiteral { + location: discarded.location(), + }); + } + + if discarded.tipo().is_result() && !discarded.is_assignment() { + self.environment + .warnings + .push(Warning::ImplicitlyDiscardedResult { + location: discarded.location(), + }); + } + } + fn get_field_map( &mut self, constructor: &TypedExpr, @@ -66,54 +223,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> { 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 { @@ -134,10 +245,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location, } => self.infer_seq(location, expressions), - UntypedExpr::Tuple { - location, elems, .. - } => self.infer_tuple(elems, location), - + // UntypedExpr::Tuple { + // location, elems, .. + // } => self.infer_tuple(elems, location), UntypedExpr::String { location, value, .. } => Ok(self.infer_string(value, location)), @@ -153,6 +263,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .. } => self.infer_fn(args, &[], *body, is_capture, return_annotation, location), + UntypedExpr::If { + location, + branches, + final_else, + } => todo!(), + UntypedExpr::Assignment { location, pattern, @@ -171,12 +287,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .. } => self.infer_try(pattern, *value, *then, &annotation, location), - UntypedExpr::Case { + UntypedExpr::When { location, subjects, clauses, .. - } => self.infer_case(subjects, clauses, location), + } => self.infer_when(subjects, clauses, location), UntypedExpr::List { location, @@ -207,15 +323,14 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .. } => 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::TupleIndex { + // location, + // index, + // tuple, + // .. + // } => self.infer_tuple_index(*tuple, index, location), + UntypedExpr::ByteArray { location, bytes } => { + Ok(self.infer_byte_array(bytes, location)) } UntypedExpr::RecordUpdate { @@ -229,9 +344,828 @@ impl<'a, 'b> ExprTyper<'a, 'b> { } } - pub fn type_from_annotation(&mut self, annotation: &Annotation) -> Result, Error> { - self.hydrator - .type_from_annotation(annotation, self.environment) + fn infer_byte_array(&mut self, bytes: Vec, location: Span) -> TypedExpr { + TypedExpr::ByteArray { + location, + bytes, + tipo: byte_array(), + } + } + + fn infer_binop( + &mut self, + name: BinOp, + left: UntypedExpr, + right: UntypedExpr, + location: Span, + ) -> Result { + let (input_type, output_type) = match &name { + BinOp::Eq | BinOp::NotEq => { + let left = self.infer(left)?; + + let right = self.infer(right)?; + + self.unify(left.tipo(), right.tipo(), right.location())?; + + return Ok(TypedExpr::BinOp { + location, + name, + tipo: bool(), + left: Box::new(left), + right: Box::new(right), + }); + } + BinOp::And => (bool(), bool()), + BinOp::Or => (bool(), bool()), + BinOp::LtInt => (int(), bool()), + BinOp::LtEqInt => (int(), bool()), + BinOp::GtEqInt => (int(), bool()), + BinOp::GtInt => (int(), bool()), + BinOp::AddInt => (int(), int()), + BinOp::SubInt => (int(), int()), + BinOp::MultInt => (int(), int()), + BinOp::DivInt => (int(), int()), + BinOp::ModInt => (int(), int()), + }; + + let left = self.infer(left)?; + + self.unify( + input_type.clone(), + left.tipo(), + left.type_defining_location(), + ) + .map_err(|e| e.operator_situation(name))?; + + let right = self.infer(right)?; + + self.unify(input_type, right.tipo(), right.type_defining_location()) + .map_err(|e| e.operator_situation(name))?; + + Ok(TypedExpr::BinOp { + location, + name, + tipo: output_type, + left: Box::new(left), + right: Box::new(right), + }) + } + + fn infer_record_update( + &mut self, + constructor: UntypedExpr, + spread: RecordUpdateSpread, + args: Vec, + location: Span, + ) -> Result { + let (module, name): (Option, String) = match self.infer(constructor.clone())? { + TypedExpr::ModuleSelect { + module_alias, + label, + .. + } => (Some(module_alias), label), + + TypedExpr::Var { name, .. } => (None, name), + + constructor => { + return Err(Error::RecordUpdateInvalidConstructor { + location: constructor.location(), + }); + } + }; + + let value_constructor = self + .environment + .get_value_constructor(module.as_ref(), &name, location)? + .clone(); + + // It must be a record with a field map for us to be able to update it + let (field_map, constructors_count) = match &value_constructor.variant { + ValueConstructorVariant::Record { + field_map: Some(field_map), + constructors_count, + .. + } => (field_map, *constructors_count), + _ => { + return Err(Error::RecordUpdateInvalidConstructor { + location: constructor.location(), + }); + } + }; + + // We can only update a record if it is the only variant of its type. + // If a record has multiple variants it cannot be safely updated as it + // could be one of the other variants. + if constructors_count != 1 { + return Err(Error::UpdateMultiConstructorType { + location: constructor.location(), + }); + } + + // The type must be a function for it to be a record constructor + let ret = match value_constructor.tipo.as_ref() { + Type::Fn { ret, .. } => ret, + _ => { + return Err(Error::RecordUpdateInvalidConstructor { + location: constructor.location(), + }) + } + }; + + let spread = self.infer(*spread.base)?; + let return_type = self.instantiate(ret.clone(), &mut HashMap::new()); + + // Check that the spread variable unifies with the return type of the constructor + self.unify(return_type, spread.tipo(), spread.location())?; + + let mut arguments = Vec::new(); + + for UntypedRecordUpdateArg { + label, + value, + location, + } in args + { + let value = self.infer(value.clone())?; + let spread_field = + self.infer_known_record_access(spread.clone(), label.to_string(), location)?; + + // Check that the update argument unifies with the corresponding + // field in the record contained within the spread variable. We + // need to check the spread, and not the constructor, in order + // to handle polymorphic types. + self.unify(spread_field.tipo(), value.tipo(), value.location())?; + + match field_map.fields.get(&label) { + None => { + panic!("Failed to lookup record field after successfully inferring that field",) + } + Some(p) => arguments.push(TypedRecordUpdateArg { + location, + label: label.to_string(), + value, + index: *p, + }), + } + } + + if arguments.is_empty() { + self.environment + .warnings + .push(Warning::NoFieldsRecordUpdate { location }); + } + + if arguments.len() == field_map.arity as usize { + self.environment + .warnings + .push(Warning::AllFieldsRecordUpdate { location }); + } + + Ok(TypedExpr::RecordUpdate { + location, + tipo: spread.tipo(), + spread: Box::new(spread), + args: arguments, + }) + } + + fn infer_negate( + &mut self, + location: Span, + value: Box, + ) -> Result { + let value = self.infer(*value)?; + + self.unify(bool(), value.tipo(), value.location())?; + + Ok(TypedExpr::Negate { + location, + value: Box::new(value), + }) + } + + fn infer_field_access( + &mut self, + container: UntypedExpr, + label: String, + access_location: Span, + ) -> Result { + // Attempt to infer the container as a record access. If that fails, we may be shadowing the name + // of an imported module, so attempt to infer the container as a module access. + // TODO: Remove this cloning + match self.infer_record_access(container.clone(), label.clone(), access_location) { + Ok(record_access) => Ok(record_access), + + Err(err) => match container { + UntypedExpr::Var { name, location, .. } => { + let module_access = + self.infer_module_access(&name, label, &location, access_location); + + // If the name is in the environment, use the original error from + // inferring the record access, so that we can suggest possible + // misspellings of field names + if self.environment.scope.contains_key(&name) { + module_access.map_err(|_| err) + } else { + module_access + } + } + _ => Err(err), + }, + } + } + + fn infer_module_access( + &mut self, + module_alias: &str, + label: String, + module_location: &Span, + select_location: Span, + ) -> Result { + let (module_name, constructor) = { + let (_, module) = self + .environment + .imported_modules + .get(module_alias) + .ok_or_else(|| Error::UnknownModule { + name: module_alias.to_string(), + location: *module_location, + imported_modules: self + .environment + .imported_modules + .keys() + .map(|t| t.to_string()) + .collect(), + })?; + + let constructor = + module + .values + .get(&label) + .ok_or_else(|| Error::UnknownModuleValue { + name: label.clone(), + location: Span { + src: SrcId::empty(), + start: module_location.end, + end: select_location.end, + }, + module_name: module.name.clone(), + value_constructors: module.values.keys().map(|t| t.to_string()).collect(), + })?; + + // Register this imported module as having been used, to inform + // warnings of unused imports later + self.environment.unused_modules.remove(module_alias); + + (module.name.clone(), constructor.clone()) + }; + + let tipo = self.instantiate(constructor.tipo, &mut HashMap::new()); + + let constructor = match &constructor.variant { + variant @ ValueConstructorVariant::ModuleFn { name, module, .. } => { + variant.to_module_value_constructor(Arc::clone(&tipo), module, name) + } + + variant @ (ValueConstructorVariant::LocalVariable { .. } + | ValueConstructorVariant::ModuleConstant { .. } + | ValueConstructorVariant::Record { .. }) => { + variant.to_module_value_constructor(Arc::clone(&tipo), &module_name, &label) + } + }; + + Ok(TypedExpr::ModuleSelect { + label, + tipo: Arc::clone(&tipo), + location: select_location, + module_name, + module_alias: module_alias.to_string(), + constructor, + }) + } + + fn infer_record_access( + &mut self, + record: UntypedExpr, + label: String, + location: Span, + ) -> Result { + // Infer the type of the (presumed) record + let record = self.infer(record)?; + + self.infer_known_record_access(record, label, location) + } + + fn infer_known_record_access( + &mut self, + record: TypedExpr, + label: String, + location: Span, + ) -> Result { + let record = Box::new(record); + + // If we don't yet know the type of the record then we cannot use any accessors + if record.tipo().is_unbound() { + return Err(Error::RecordAccessUnknownType { + location: record.location(), + }); + } + + // Error constructor helper function + let unknown_field = |fields| Error::UnknownRecordField { + situation: None, + typ: record.tipo(), + location, + label: label.clone(), + fields, + }; + + // Check to see if it's a Type that can have accessible fields + let accessors = match collapse_links(record.tipo()).as_ref() { + // A type in the current module which may have fields + Type::App { module, name, .. } if module == self.environment.current_module => { + self.environment.accessors.get(name) + } + + // A type in another module which may have fields + Type::App { module, name, .. } => self + .environment + .importable_modules + .get(module) + .and_then(|module| module.accessors.get(name)), + + _something_without_fields => return Err(unknown_field(vec![])), + } + .ok_or_else(|| unknown_field(vec![]))?; + + // Find the accessor, if the type has one with the same label + let RecordAccessor { index, label, tipo } = accessors + .accessors + .get(&label) + .ok_or_else(|| { + unknown_field(accessors.accessors.keys().map(|t| t.to_string()).collect()) + })? + .clone(); + + // Unify the record type with the accessor's stored copy of the record type. + // This ensure that the type parameters of the retrieved value have the correct + // types for this instance of the record. + let accessor_record_type = accessors.tipo.clone(); + + let mut type_vars = HashMap::new(); + + let accessor_record_type = self.instantiate(accessor_record_type, &mut type_vars); + + let tipo = self.instantiate(tipo, &mut type_vars); + + self.unify(accessor_record_type, record.tipo(), record.location())?; + + Ok(TypedExpr::RecordAccess { + record, + label, + index, + location, + tipo, + }) + } + + fn infer_arg( + &mut self, + arg: UntypedArg, + expected: Option>, + ) -> Result { + let Arg { + arg_name, + annotation, + location, + .. + } = arg; + + let tipo = annotation + .clone() + .map(|t| self.type_from_annotation(&t)) + .unwrap_or_else(|| Ok(self.new_unbound_var()))?; + + // If we know the expected type of the argument from its contextual + // usage then unify the newly constructed type with the expected type. + // We do this here because then there is more type information for the + // function being type checked, resulting in better type errors and the + // record field access syntax working. + if let Some(expected) = expected { + self.unify(expected, tipo.clone(), location)?; + } + + Ok(Arg { + arg_name, + location, + annotation, + tipo, + }) + } + + fn infer_assignment( + &mut self, + pattern: UntypedPattern, + value: UntypedExpr, + kind: AssignmentKind, + annotation: &Option, + location: Span, + ) -> Result { + let value = self.in_new_scope(|value_typer| value_typer.infer(value))?; + let value_typ = value.tipo(); + + // Ensure the pattern matches the type of the value + let pattern = PatternTyper::new(self.environment, &self.hydrator) + .unify(pattern, value_typ.clone())?; + + // Check that any type annotation is accurate. + if let Some(ann) = annotation { + let ann_typ = self + .type_from_annotation(ann) + .map(|t| self.instantiate(t, &mut HashMap::new()))?; + + self.unify(ann_typ, value_typ.clone(), value.type_defining_location())?; + } + + // We currently only do limited exhaustiveness checking of custom types + // at the top level of patterns. + // Do not perform exhaustiveness checking if user explicitly used `assert`. + if kind != AssignmentKind::Assert { + if let Err(unmatched) = self.environment.check_exhaustiveness( + vec![pattern.clone()], + collapse_links(value_typ.clone()), + location, + ) { + return Err(Error::NotExhaustivePatternMatch { + location, + unmatched, + }); + } + } + + Ok(TypedExpr::Assignment { + location, + tipo: value_typ, + kind, + pattern, + value: Box::new(value), + }) + } + + fn infer_call( + &mut self, + fun: UntypedExpr, + args: Vec>, + location: Span, + ) -> Result { + let (fun, args, tipo) = self + .do_infer_call(fun, args, location) + .map_err(|e| e.call_situation())?; + + Ok(TypedExpr::Call { + location, + tipo, + args, + fun: Box::new(fun), + }) + } + + fn infer_call_argument( + &mut self, + value: UntypedExpr, + tipo: Arc, + ) -> Result { + let tipo = collapse_links(tipo); + + let value = match (&*tipo, value) { + // If the argument is expected to be a function and we are passed a + // function literal with the correct number of arguments then we + // have special handling of this argument, passing in information + // about what the expected arguments are. This extra information + // when type checking the function body means that the + // `record.field` access syntax can be used, and improves error + // messages. + ( + Type::Fn { + args: expected_arguments, + .. + }, + UntypedExpr::Fn { + arguments, + body, + return_annotation, + location, + is_capture: false, + .. + }, + ) if expected_arguments.len() == arguments.len() => self.infer_fn( + arguments, + expected_arguments, + *body, + false, + return_annotation, + location, + ), + + // Otherwise just perform normal type inference. + (_, value) => self.infer(value), + }?; + + self.unify(tipo, value.tipo(), value.location())?; + + Ok(value) + } + + fn infer_clause( + &mut self, + clause: UntypedClause, + subjects: &[Arc], + ) -> Result { + let Clause { + pattern, + alternative_patterns, + guard, + then, + location, + } = clause; + + let (guard, then, typed_pattern, typed_alternatives) = + self.in_new_scope(|clause_typer| { + // Check the types + let (typed_pattern, typed_alternatives) = clause_typer.infer_clause_pattern( + pattern, + alternative_patterns, + subjects, + &location, + )?; + + let guard = clause_typer.infer_optional_clause_guard(guard)?; + + let then = clause_typer.infer(then)?; + + Ok((guard, then, typed_pattern, typed_alternatives)) + })?; + + Ok(Clause { + location, + pattern: typed_pattern, + alternative_patterns: typed_alternatives, + guard, + then, + }) + } + + fn infer_clause_guard(&mut self, guard: UntypedClauseGuard) -> Result { + match guard { + ClauseGuard::Var { location, name, .. } => { + let constructor = self.infer_value_constructor(&None, &name, &location)?; + + // We cannot support all values in guard expressions as the BEAM does not + match &constructor.variant { + ValueConstructorVariant::LocalVariable { .. } => (), + + ValueConstructorVariant::ModuleFn { .. } + | ValueConstructorVariant::Record { .. } => { + return Err(Error::NonLocalClauseGuardVariable { location, name }); + } + + ValueConstructorVariant::ModuleConstant { literal, .. } => { + return Ok(ClauseGuard::Constant(literal.clone())) + } + }; + + Ok(ClauseGuard::Var { + location, + name, + tipo: constructor.tipo, + }) + } + + // ClauseGuard::TupleIndex { + // location, + // tuple, + // index, + // .. + // } => { + // let tuple = self.infer_clause_guard(*tuple)?; + // match tuple.type_().as_ref() { + // Type::Tuple { elems } => { + // let type_ = elems + // .get(index as usize) + // .ok_or(Error::OutOfBoundsTupleIndex { + // location, + // index, + // size: elems.len(), + // })? + // .clone(); + // Ok(ClauseGuard::TupleIndex { + // location, + // index, + // type_, + // tuple: Box::new(tuple), + // }) + // } + + // typ if typ.is_unbound() => Err(Error::NotATupleUnbound { + // location: tuple.location(), + // }), + + // _ => Err(Error::NotATuple { + // location: tuple.location(), + // given: tuple.type_(), + // }), + // } + // } + ClauseGuard::And { + location, + left, + right, + .. + } => { + let left = self.infer_clause_guard(*left)?; + + self.unify(bool(), left.tipo(), left.location())?; + + let right = self.infer_clause_guard(*right)?; + + self.unify(bool(), right.tipo(), right.location())?; + + Ok(ClauseGuard::And { + location, + left: Box::new(left), + right: Box::new(right), + }) + } + + ClauseGuard::Or { + location, + left, + right, + .. + } => { + let left = self.infer_clause_guard(*left)?; + + self.unify(bool(), left.tipo(), left.location())?; + + let right = self.infer_clause_guard(*right)?; + + self.unify(bool(), right.tipo(), right.location())?; + + Ok(ClauseGuard::Or { + location, + left: Box::new(left), + right: Box::new(right), + }) + } + + ClauseGuard::Equals { + location, + left, + right, + .. + } => { + let left = self.infer_clause_guard(*left)?; + let right = self.infer_clause_guard(*right)?; + + self.unify(left.tipo(), right.tipo(), location)?; + + Ok(ClauseGuard::Equals { + location, + left: Box::new(left), + right: Box::new(right), + }) + } + + ClauseGuard::NotEquals { + location, + left, + right, + .. + } => { + let left = self.infer_clause_guard(*left)?; + let right = self.infer_clause_guard(*right)?; + + self.unify(left.tipo(), right.tipo(), location)?; + + Ok(ClauseGuard::NotEquals { + location, + left: Box::new(left), + right: Box::new(right), + }) + } + + ClauseGuard::GtInt { + location, + left, + right, + .. + } => { + let left = self.infer_clause_guard(*left)?; + + self.unify(int(), left.tipo(), left.location())?; + + let right = self.infer_clause_guard(*right)?; + + self.unify(int(), right.tipo(), right.location())?; + + Ok(ClauseGuard::GtInt { + location, + left: Box::new(left), + right: Box::new(right), + }) + } + + ClauseGuard::GtEqInt { + location, + left, + right, + .. + } => { + let left = self.infer_clause_guard(*left)?; + + self.unify(int(), left.tipo(), left.location())?; + + let right = self.infer_clause_guard(*right)?; + + self.unify(int(), right.tipo(), right.location())?; + + Ok(ClauseGuard::GtEqInt { + location, + left: Box::new(left), + right: Box::new(right), + }) + } + + ClauseGuard::LtInt { + location, + left, + right, + .. + } => { + let left = self.infer_clause_guard(*left)?; + + self.unify(int(), left.tipo(), left.location())?; + + let right = self.infer_clause_guard(*right)?; + + self.unify(int(), right.tipo(), right.location())?; + + Ok(ClauseGuard::LtInt { + location, + left: Box::new(left), + right: Box::new(right), + }) + } + + ClauseGuard::LtEqInt { + location, + left, + right, + .. + } => { + let left = self.infer_clause_guard(*left)?; + + self.unify(int(), left.tipo(), left.location())?; + + let right = self.infer_clause_guard(*right)?; + + self.unify(int(), right.tipo(), right.location())?; + + Ok(ClauseGuard::LtEqInt { + location, + left: Box::new(left), + right: Box::new(right), + }) + } + + ClauseGuard::Constant(constant) => { + self.infer_const(&None, constant).map(ClauseGuard::Constant) + } + } + } + + fn infer_clause_pattern( + &mut self, + pattern: UntypedMultiPattern, + alternatives: Vec, + subjects: &[Arc], + location: &Span, + ) -> Result<(TypedMultiPattern, Vec), Error> { + let mut pattern_typer = PatternTyper::new(self.environment, &self.hydrator); + + let typed_pattern = pattern_typer.infer_multi_pattern(pattern, subjects, location)?; + + // Each case clause has one or more patterns that may match the + // subject in order for the clause to be selected, so we must type + // check every pattern. + let mut typed_alternatives = Vec::with_capacity(alternatives.len()); + + for m in alternatives { + typed_alternatives + .push(pattern_typer.infer_alternative_multi_pattern(m, subjects, location)?); + } + + Ok((typed_pattern, typed_alternatives)) } // TODO: extract the type annotation checking into a infer_module_const @@ -345,7 +1279,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .get(module_name) .expect("Failed to find previously located module import") .1 - .name; + .name + .clone(); let module_value_constructor = ModuleValueConstructor::Record { name: name.clone(), @@ -487,6 +1422,257 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }) } + fn infer_fn( + &mut self, + args: Vec, + expected_args: &[Arc], + body: UntypedExpr, + is_capture: bool, + return_annotation: Option, + location: Span, + ) -> Result { + let (args, body) = self.do_infer_fn(args, expected_args, body, &return_annotation)?; + + let args_types = args.iter().map(|a| a.tipo.clone()).collect(); + + let tipo = function(args_types, body.tipo()); + + Ok(TypedExpr::Fn { + location, + tipo, + is_capture, + args, + body: Box::new(body), + return_annotation, + }) + } + + 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)) + } + + fn infer_int(&mut self, value: String, location: Span) -> TypedExpr { + TypedExpr::Int { + location, + value, + tipo: int(), + } + } + + fn infer_list( + &mut self, + elements: Vec, + tail: Option>, + location: Span, + ) -> Result { + let tipo = self.new_unbound_var(); + + let mut elems = Vec::new(); + + for elem in elements.into_iter() { + let element = self.infer(elem)?; + + // Ensure they all have the same type + self.unify(tipo.clone(), element.tipo(), location)?; + + elems.push(element) + } + + // Type check the ..tail, if there is one + let tipo = list(tipo); + + let tail = match tail { + Some(tail) => { + let tail = self.infer(*tail)?; + + // Ensure the tail has the same type as the preceeding elements + self.unify(tipo.clone(), tail.tipo(), location)?; + + Some(Box::new(tail)) + } + None => None, + }; + + Ok(TypedExpr::List { + location, + tipo, + elements: elems, + tail, + }) + } + + fn infer_optional_clause_guard( + &mut self, + guard: Option, + ) -> Result, Error> { + match guard { + // If there is no guard we do nothing + None => Ok(None), + + // If there is a guard we assert that it is of type Bool + Some(guard) => { + let guard = self.infer_clause_guard(guard)?; + + self.unify(bool(), guard.tipo(), guard.location())?; + + Ok(Some(guard)) + } + } + } + + fn infer_pipeline(&mut self, expressions: Vec1) -> Result { + PipeTyper::infer(self, expressions) + } + + fn infer_seq(&mut self, location: Span, untyped: Vec) -> Result { + let count = untyped.len(); + + let mut expressions = Vec::with_capacity(count); + + for (i, expression) in untyped.into_iter().enumerate() { + let expression = self.infer(expression)?; + // This isn't the final expression in the sequence, so call the + // `expression_discarded` function to see if anything is being + // discarded that we think shouldn't be. + if i < count - 1 { + self.expression_discarded(&expression); + } + + expressions.push(expression); + } + + Ok(TypedExpr::Sequence { + location, + expressions, + }) + } + + fn infer_string(&mut self, value: String, location: Span) -> TypedExpr { + TypedExpr::String { + location, + value, + tipo: string(), + } + } + + fn infer_todo(&mut self, location: Span, kind: TodoKind, label: Option) -> TypedExpr { + let tipo = self.new_unbound_var(); + + self.environment.warnings.push(Warning::Todo { + kind, + location, + tipo: tipo.clone(), + }); + + TypedExpr::Todo { + location, + label, + tipo, + } + } + + fn infer_try( + &mut self, + pattern: UntypedPattern, + value: UntypedExpr, + then: UntypedExpr, + annotation: &Option, + location: Span, + ) -> Result { + let value = self.in_new_scope(|value_typer| value_typer.infer(value))?; + + let value_type = self.new_unbound_var(); + let try_error_type = self.new_unbound_var(); + + // Ensure that the value is a result + { + let v = value_type.clone(); + + let e = try_error_type.clone(); + + self.unify(result(v, e), value.tipo(), value.type_defining_location())?; + }; + + // Ensure the pattern matches the type of the value + let pattern = PatternTyper::new(self.environment, &self.hydrator) + .unify(pattern, value_type.clone())?; + + // Check the type of the following code + let then = self.infer(then)?; + + let tipo = then.tipo(); + + // Ensure that a Result with the right error type is returned for `try` + { + let t = self.new_unbound_var(); + + self.unify( + result(t, try_error_type), + tipo.clone(), + then.type_defining_location(), + ) + .map_err(|e| e.inconsistent_try(tipo.is_result()))?; + } + + // Check that any type annotation is accurate. + if let Some(ann) = annotation { + let ann_typ = self + .type_from_annotation(ann) + .map(|t| self.instantiate(t, &mut HashMap::new()))?; + + self.unify(ann_typ, value_type, value.type_defining_location())?; + } + + Ok(TypedExpr::Try { + location, + tipo, + pattern, + value: Box::new(value), + then: Box::new(then), + }) + } + fn infer_value_constructor( &mut self, module: &Option, @@ -571,6 +1757,72 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }) } + fn infer_var(&mut self, name: String, location: Span) -> Result { + let constructor = self.infer_value_constructor(&None, &name, &location)?; + + Ok(TypedExpr::Var { + constructor, + location, + name, + }) + } + + fn infer_when( + &mut self, + subjects: Vec, + clauses: Vec, + location: Span, + ) -> Result { + let subjects_count = subjects.len(); + + let mut typed_subjects = Vec::with_capacity(subjects_count); + let mut subject_types = Vec::with_capacity(subjects_count); + let mut typed_clauses = Vec::with_capacity(clauses.len()); + + let return_type = self.new_unbound_var(); + + for subject in subjects { + let subject = self.in_new_scope(|subject_typer| { + let subject = subject_typer.infer(subject)?; + + Ok(subject) + })?; + + subject_types.push(subject.tipo()); + + typed_subjects.push(subject); + } + + for clause in clauses { + let typed_clause = self.infer_clause(clause, &subject_types)?; + + self.unify( + return_type.clone(), + typed_clause.then.tipo(), + typed_clause.location(), + ) + .map_err(|e| e.case_clause_mismatch())?; + + typed_clauses.push(typed_clause); + } + + if let Err(unmatched) = + self.check_when_exhaustiveness(subjects_count, &subject_types, &typed_clauses, location) + { + return Err(Error::NotExhaustivePatternMatch { + location, + unmatched, + }); + } + + Ok(TypedExpr::When { + location, + tipo: return_type, + subjects: typed_subjects, + clauses: typed_clauses, + }) + } + fn instantiate(&mut self, t: Arc, ids: &mut HashMap>) -> Arc { self.environment.instantiate(t, ids, &self.hydrator) } @@ -591,6 +1843,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> { self.environment.new_unbound_var() } + pub fn type_from_annotation(&mut self, annotation: &Annotation) -> Result, Error> { + self.hydrator + .type_from_annotation(annotation, self.environment) + } + 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/hydrator.rs b/crates/lang/src/tipo/hydrator.rs index 9db2028a..7ee7ccf6 100644 --- a/crates/lang/src/tipo/hydrator.rs +++ b/crates/lang/src/tipo/hydrator.rs @@ -34,6 +34,12 @@ pub struct ScopeResetData { rigid_type_names: HashMap, } +impl Default for Hydrator { + fn default() -> Self { + Self::new() + } +} + impl Hydrator { pub fn new() -> Hydrator { Hydrator { diff --git a/crates/lang/src/tipo/infer.rs b/crates/lang/src/tipo/infer.rs index f5338b4e..7429a9f2 100644 --- a/crates/lang/src/tipo/infer.rs +++ b/crates/lang/src/tipo/infer.rs @@ -61,7 +61,8 @@ impl UntypedModule { let mut definitions = Vec::with_capacity(self.definitions.len()); let mut consts = vec![]; let mut not_consts = vec![]; - for def in self.into_definitions() { + + for def in self.definitions().cloned() { match def { Definition::ModuleConstant { .. } => consts.push(def), diff --git a/crates/lang/src/tipo/pattern.rs b/crates/lang/src/tipo/pattern.rs new file mode 100644 index 00000000..f9ea9b56 --- /dev/null +++ b/crates/lang/src/tipo/pattern.rs @@ -0,0 +1,573 @@ +///! Type inference and checking of patterns used in case expressions +///! and variables bindings. +use std::{ + collections::{HashMap, HashSet}, + ops::Deref, + sync::Arc, +}; + +use itertools::Itertools; + +use super::{ + environment::{assert_no_labeled_arguments, EntityKind, Environment}, + error::Error, + hydrator::Hydrator, + PatternConstructor, Type, ValueConstructor, ValueConstructorVariant, +}; +use crate::{ + ast::{CallArg, Pattern, Span, SrcId, TypedPattern, UntypedMultiPattern, UntypedPattern}, + builtins::{int, list, string}, +}; + +pub struct PatternTyper<'a, 'b> { + environment: &'a mut Environment<'b>, + hydrator: &'a Hydrator, + mode: PatternMode, + initial_pattern_vars: HashSet, +} + +enum PatternMode { + Initial, + Alternative(Vec), +} + +impl<'a, 'b> PatternTyper<'a, 'b> { + pub fn new(environment: &'a mut Environment<'b>, hydrator: &'a Hydrator) -> Self { + Self { + environment, + hydrator, + mode: PatternMode::Initial, + initial_pattern_vars: HashSet::new(), + } + } + + fn insert_variable( + &mut self, + name: &str, + typ: Arc, + location: Span, + err_location: Span, + ) -> Result<(), Error> { + match &mut self.mode { + PatternMode::Initial => { + // Register usage for the unused variable detection + self.environment + .init_usage(name.to_string(), EntityKind::Variable, location); + + // Ensure there are no duplicate variable names in the pattern + if self.initial_pattern_vars.contains(name) { + return Err(Error::DuplicateVarInPattern { + name: name.to_string(), + location: err_location, + }); + } + // Record that this variable originated in this pattern so any + // following alternative patterns can be checked to ensure they + // have the same variables. + self.initial_pattern_vars.insert(name.to_string()); + + // And now insert the variable for use in the code that comes + // after the pattern. + self.environment.insert_variable( + name.to_string(), + ValueConstructorVariant::LocalVariable { location }, + typ, + ); + Ok(()) + } + + PatternMode::Alternative(assigned) => { + match self.environment.scope.get(name) { + // This variable was defined in the Initial multi-pattern + Some(initial) if self.initial_pattern_vars.contains(name) => { + assigned.push(name.to_string()); + let initial_typ = initial.tipo.clone(); + self.environment.unify(initial_typ, typ, err_location) + } + + // This variable was not defined in the Initial multi-pattern + _ => Err(Error::ExtraVarInAlternativePattern { + name: name.to_string(), + location: err_location, + }), + } + } + } + } + + pub fn infer_alternative_multi_pattern( + &mut self, + multi_pattern: UntypedMultiPattern, + subjects: &[Arc], + location: &Span, + ) -> Result, Error> { + self.mode = PatternMode::Alternative(vec![]); + let typed_multi = self.infer_multi_pattern(multi_pattern, subjects, location)?; + match &self.mode { + PatternMode::Initial => panic!("Pattern mode switched from Alternative to Initial"), + PatternMode::Alternative(assigned) + if assigned.len() != self.initial_pattern_vars.len() => + { + for name in assigned { + self.initial_pattern_vars.remove(name); + } + Err(Error::MissingVarInAlternativePattern { + location: *location, + // It is safe to use expect here as we checked the length above + name: self + .initial_pattern_vars + .iter() + .next() + .expect("Getting undefined pattern variable") + .clone(), + }) + } + PatternMode::Alternative(_) => Ok(typed_multi), + } + } + + pub fn infer_multi_pattern( + &mut self, + multi_pattern: UntypedMultiPattern, + subjects: &[Arc], + location: &Span, + ) -> Result, Error> { + // If there are N subjects the multi-pattern is expected to be N patterns + if subjects.len() != multi_pattern.len() { + return Err(Error::IncorrectNumClausePatterns { + location: *location, + expected: subjects.len(), + given: multi_pattern.len(), + }); + } + + // Unify each pattern in the multi-pattern with the corresponding subject + let mut typed_multi = Vec::with_capacity(multi_pattern.len()); + for (pattern, subject_type) in multi_pattern.into_iter().zip(subjects) { + let pattern = self.unify(pattern, subject_type.clone())?; + typed_multi.push(pattern); + } + Ok(typed_multi) + } + + // fn infer_pattern_bit_string( + // &mut self, + // mut segments: Vec, + // location: Span, + // ) -> Result { + // let last_segment = segments.pop(); + + // let mut typed_segments: Vec<_> = segments + // .into_iter() + // .map(|s| self.infer_pattern_segment(s, false)) + // .try_collect()?; + + // if let Some(s) = last_segment { + // let typed_last_segment = self.infer_pattern_segment(s, true)?; + // typed_segments.push(typed_last_segment) + // } + + // Ok(TypedPattern::BitString { + // location, + // segments: typed_segments, + // }) + // } + + // fn infer_pattern_segment( + // &mut self, + // segment: UntypedPatternBitStringSegment, + // is_last_segment: bool, + // ) -> Result { + // let UntypedPatternBitStringSegment { + // location, + // options, + // value, + // .. + // } = segment; + + // let options: Vec<_> = options + // .into_iter() + // .map(|o| infer_bit_string_segment_option(o, |value, typ| self.unify(value, typ))) + // .try_collect()?; + + // let segment_type = bit_string::type_options_for_pattern(&options, !is_last_segment) + // .map_err(|error| Error::BitStringSegmentError { + // error: error.error, + // location: error.location, + // })?; + + // let typ = { + // match value.deref() { + // Pattern::Var { .. } if segment_type == string() => { + // Err(Error::BitStringSegmentError { + // error: bit_string::ErrorType::VariableUtfSegmentInPattern, + // location, + // }) + // } + // _ => Ok(segment_type), + // } + // }?; + // let typed_value = self.unify(*value, typ.clone())?; + + // Ok(BitStringSegment { + // location, + // value: Box::new(typed_value), + // options, + // type_: typ, + // }) + // } + + /// When we have an assignment or a case expression we unify the pattern with the + /// inferred type of the subject in order to determine what variables to insert + /// into the environment (or to detect a type error). + pub fn unify( + &mut self, + pattern: UntypedPattern, + tipo: Arc, + ) -> Result { + match pattern { + Pattern::Discard { name, location } => Ok(Pattern::Discard { name, location }), + + Pattern::Var { name, location, .. } => { + self.insert_variable(&name, tipo, location, location)?; + + Ok(Pattern::Var { name, location }) + } + + Pattern::VarUsage { name, location, .. } => { + let ValueConstructor { tipo, .. } = self + .environment + .get_variable(&name) + .cloned() + .ok_or_else(|| Error::UnknownVariable { + location, + name: name.to_string(), + variables: self.environment.local_value_names(), + })?; + + self.environment.increment_usage(&name); + + let tipo = self + .environment + .instantiate(tipo, &mut HashMap::new(), self.hydrator); + + self.environment.unify(int(), tipo.clone(), location)?; + + Ok(Pattern::VarUsage { + name, + location, + tipo, + }) + } + + // Pattern::Concatenate { + // location, + // left_location, + // right_location, + // left_side_string, + // right_side_assignment, + // } => { + // // The entire concatenate pattern must be a string + // self.environment.unify(tipo, string(), location)?; + + // // The right hand side may assign a variable, which is the suffix of the string + // if let AssignName::Variable(right) = &right_side_assignment { + // self.insert_variable(right.as_ref(), string(), right_location, location)?; + // }; + + // Ok(Pattern::Concatenate { + // location, + // left_location, + // right_location, + // left_side_string, + // right_side_assignment, + // }) + // } + Pattern::Assign { + name, + pattern, + location, + } => { + self.insert_variable(&name, tipo.clone(), location, pattern.location())?; + + let pattern = self.unify(*pattern, tipo)?; + + Ok(Pattern::Assign { + name, + pattern: Box::new(pattern), + location, + }) + } + + Pattern::Int { location, value } => { + self.environment.unify(tipo, int(), location)?; + + Ok(Pattern::Int { location, value }) + } + + Pattern::String { location, value } => { + self.environment.unify(tipo, string(), location)?; + + Ok(Pattern::String { location, value }) + } + + Pattern::List { + location, + elements, + tail, + } => match tipo.get_app_args(true, "", "List", 1, self.environment) { + Some(args) => { + let tipo = args + .get(0) + .expect("Failed to get type argument of List") + .clone(); + + let elements = elements + .into_iter() + .map(|element| self.unify(element, tipo.clone())) + .try_collect()?; + + let tail = match tail { + Some(tail) => Some(Box::new(self.unify(*tail, list(tipo))?)), + None => None, + }; + + Ok(Pattern::List { + location, + elements, + tail, + }) + } + + None => Err(Error::CouldNotUnify { + given: list(self.environment.new_unbound_var()), + expected: tipo.clone(), + situation: None, + location, + rigid_type_names: HashMap::new(), + }), + }, + + // Pattern::Tuple { elems, location } => match collapse_links(tipo.clone()).deref() { + // Type::Tuple { elems: type_elems } => { + // if elems.len() != type_elems.len() { + // return Err(Error::IncorrectArity { + // labels: vec![], + // location, + // expected: type_elems.len(), + // given: elems.len(), + // }); + // } + + // let elems = elems + // .into_iter() + // .zip(type_elems) + // .map(|(pattern, typ)| self.unify(pattern, typ.clone())) + // .try_collect()?; + + // Ok(Pattern::Tuple { elems, location }) + // } + + // Type::Var { .. } => { + // let elems_types: Vec<_> = (0..(elems.len())) + // .map(|_| self.environment.new_unbound_var()) + // .collect(); + // self.environment + // .unify(tuple(elems_types.clone()), type_) + // .map_err(|e| convert_unify_error(e, location))?; + // let elems = elems + // .into_iter() + // .zip(elems_types) + // .map(|(pattern, type_)| self.unify(pattern, type_)) + // .try_collect()?; + // Ok(Pattern::Tuple { elems, location }) + // } + + // _ => { + // let elems_types = (0..(elems.len())) + // .map(|_| self.environment.new_unbound_var()) + // .collect(); + + // Err(Error::CouldNotUnify { + // given: tuple(elems_types), + // expected: type_, + // situation: None, + // location, + // rigid_type_names: hashmap![], + // }) + // } + // }, + // Pattern::BitString { location, segments } => { + // self.environment + // .unify(type_, bit_string()) + // .map_err(|e| convert_unify_error(e, location))?; + // self.infer_pattern_bit_string(segments, location) + // } + Pattern::Constructor { + location, + module, + name, + arguments: mut pattern_args, + with_spread, + .. + } => { + // Register the value as seen for detection of unused values + self.environment.increment_usage(&name); + + let cons = + self.environment + .get_value_constructor(module.as_ref(), &name, location)?; + + match cons.field_map() { + // The fun has a field map so labelled arguments may be present and need to be reordered. + Some(field_map) => { + if with_spread { + // Using the spread operator when you have already provided variables for all of the + // record's fields throws an error + if pattern_args.len() == field_map.arity as usize { + return Err(Error::UnnecessarySpreadOperator { + location: Span { + src: SrcId::empty(), + start: location.end - 3, + end: location.end - 1, + }, + arity: field_map.arity as usize, + }); + } + + // The location of the spread operator itself + let spread_location = Span { + src: SrcId::empty(), + start: location.end - 3, + end: location.end - 1, + }; + + // Insert discard variables to match the unspecified fields + // In order to support both positional and labelled arguments we have to insert + // them after all positional variables and before the labelled ones. This means + // we have calculate that index and then insert() the discards. It would be faster + // if we could put the discards anywhere which would let us use push(). + // Potential future optimisation. + let index_of_first_labelled_arg = pattern_args + .iter() + .position(|a| a.label.is_some()) + .unwrap_or(pattern_args.len()); + + while pattern_args.len() < field_map.arity as usize { + let new_call_arg = CallArg { + value: Pattern::Discard { + name: "_".to_string(), + location: spread_location, + }, + location: spread_location, + label: None, + }; + + pattern_args.insert(index_of_first_labelled_arg, new_call_arg); + } + } + + field_map.reorder(&mut pattern_args, location)? + } + + // The fun has no field map and so we error if arguments have been labelled + None => assert_no_labeled_arguments(&pattern_args)?, + } + + let constructor_typ = cons.tipo.clone(); + let constructor = match cons.variant { + ValueConstructorVariant::Record { ref name, .. } => { + PatternConstructor::Record { + name: name.clone(), + field_map: cons.field_map().cloned(), + } + } + ValueConstructorVariant::LocalVariable { .. } + | ValueConstructorVariant::ModuleConstant { .. } + | ValueConstructorVariant::ModuleFn { .. } => { + panic!("Unexpected value constructor type for a constructor pattern.",) + } + }; + + let instantiated_constructor_type = self.environment.instantiate( + constructor_typ, + &mut HashMap::new(), + self.hydrator, + ); + match instantiated_constructor_type.deref() { + Type::Fn { args, ret } => { + if args.len() == pattern_args.len() { + let pattern_args = pattern_args + .into_iter() + .zip(args) + .map(|(arg, typ)| { + let CallArg { + value, + location, + label, + } = arg; + + let value = self.unify(value, typ.clone())?; + + Ok(CallArg { + value, + location, + label, + }) + }) + .try_collect()?; + + self.environment.unify(tipo, ret.clone(), location)?; + + Ok(Pattern::Constructor { + location, + module, + name, + arguments: pattern_args, + constructor, + with_spread, + tipo: instantiated_constructor_type, + }) + } else { + Err(Error::IncorrectArity { + labels: vec![], + location, + expected: args.len(), + given: pattern_args.len(), + }) + } + } + + Type::App { .. } => { + if pattern_args.is_empty() { + self.environment.unify( + tipo, + instantiated_constructor_type.clone(), + location, + )?; + + Ok(Pattern::Constructor { + location, + module, + name, + arguments: vec![], + constructor, + with_spread, + tipo: instantiated_constructor_type, + }) + } else { + Err(Error::IncorrectArity { + labels: vec![], + location, + expected: 0, + given: pattern_args.len(), + }) + } + } + + _ => panic!("Unexpected constructor type for a constructor pattern.",), + } + } + } + } +} diff --git a/crates/lang/src/tipo/pipe.rs b/crates/lang/src/tipo/pipe.rs new file mode 100644 index 00000000..e9fe3cee --- /dev/null +++ b/crates/lang/src/tipo/pipe.rs @@ -0,0 +1,313 @@ +use std::sync::Arc; + +use vec1::Vec1; + +use crate::{ + ast::{AssignmentKind, CallArg, Pattern, Span, SrcId, PIPE_VARIABLE}, + builtins::function, + expr::{TypedExpr, UntypedExpr}, +}; + +use super::{ + error::{Error, UnifyErrorSituation}, + expr::ExprTyper, + Type, ValueConstructor, ValueConstructorVariant, +}; + +#[derive(Debug)] +pub(crate) struct PipeTyper<'a, 'b, 'c> { + size: usize, + argument_type: Arc, + argument_location: Span, + location: Span, + expressions: Vec, + expr_typer: &'a mut ExprTyper<'b, 'c>, +} + +impl<'a, 'b, 'c> PipeTyper<'a, 'b, 'c> { + pub fn infer( + expr_typer: &'a mut ExprTyper<'b, 'c>, + expressions: Vec1, + ) -> Result { + let size = expressions.len(); + + let end = &expressions[..] + .last() + // The vec is non-empty, this indexing can never fail + .expect("Empty pipeline in typer") + .location() + .end; + + let mut expressions = expressions.into_iter(); + + let first = expr_typer.infer(expressions.next().expect("Empty pipeline in typer"))?; + + let mut typer = Self { + size, + expr_typer, + argument_type: first.tipo(), + argument_location: first.location(), + location: Span { + src: SrcId::empty(), + start: first.location().start, + end: *end, + }, + expressions: Vec::with_capacity(size), + }; + // No need to update self.argument_* as we set it above + typer.push_assignment_no_update(first); + + // Perform the type checking + typer.infer_expressions(expressions) + } + + fn infer_expressions( + mut self, + expressions: impl IntoIterator, + ) -> Result { + let result = self.infer_each_expression(expressions); + + // Clean-up the pipe variables inserted so they cannot be used outside this pipeline + let _ = self.expr_typer.environment.scope.remove(PIPE_VARIABLE); + + // Return any errors after clean-up + result?; + + Ok(TypedExpr::Pipeline { + expressions: self.expressions, + location: self.location, + }) + } + + fn infer_each_expression( + &mut self, + expressions: impl IntoIterator, + ) -> Result<(), Error> { + for (i, call) in expressions.into_iter().enumerate() { + let call = match call { + // left |> right(..args) + UntypedExpr::Call { + fun, + arguments, + location, + .. + } => { + let fun = self.expr_typer.infer(*fun)?; + + match fun.tipo().fn_arity() { + // Rewrite as right(left, ..args) + Some(arity) if arity == arguments.len() + 1 => { + self.infer_insert_pipe(fun, arguments, location)? + } + + // Rewrite as right(..args)(left) + _ => self.infer_apply_to_call_pipe(fun, arguments, location)?, + } + } + + // right(left) + call => self.infer_apply_pipe(call)?, + }; + + if i + 2 == self.size { + self.expressions.push(call); + } else { + self.push_assignment(call); + } + } + + Ok(()) + } + + /// Create a call argument that can be used to refer to the value on the + /// left hand side of the pipe + fn typed_left_hand_value_variable_call_argument(&self) -> CallArg { + CallArg { + label: None, + location: self.argument_location, + value: self.typed_left_hand_value_variable(), + } + } + + /// Create a call argument that can be used to refer to the value on the + /// left hand side of the pipe + fn untyped_left_hand_value_variable_call_argument(&self) -> CallArg { + CallArg { + label: None, + location: self.argument_location, + value: self.untyped_left_hand_value_variable(), + } + } + + /// Create a variable that can be used to refer to the value on the left + /// hand side of the pipe + fn typed_left_hand_value_variable(&self) -> TypedExpr { + TypedExpr::Var { + location: self.argument_location, + name: PIPE_VARIABLE.to_string(), + constructor: ValueConstructor { + public: true, + tipo: self.argument_type.clone(), + variant: ValueConstructorVariant::LocalVariable { + location: self.argument_location, + }, + }, + } + } + + /// Create a variable that can be used to refer to the value on the left + /// hand side of the pipe + fn untyped_left_hand_value_variable(&self) -> UntypedExpr { + UntypedExpr::Var { + location: self.argument_location, + name: PIPE_VARIABLE.to_string(), + } + } + + /// Push an assignment for the value on the left hand side of the pipe + fn push_assignment(&mut self, expression: TypedExpr) { + self.argument_type = expression.tipo(); + self.argument_location = expression.location(); + self.push_assignment_no_update(expression) + } + + fn push_assignment_no_update(&mut self, expression: TypedExpr) { + let location = expression.location(); + + // Insert the variable for use in type checking the rest of the pipeline + self.expr_typer.environment.insert_variable( + PIPE_VARIABLE.to_string(), + ValueConstructorVariant::LocalVariable { location }, + expression.tipo(), + ); + + // Add the assignment to the AST + let assignment = TypedExpr::Assignment { + location, + tipo: expression.tipo(), + kind: AssignmentKind::Let, + value: Box::new(expression), + pattern: Pattern::Var { + location, + name: PIPE_VARIABLE.to_string(), + }, + }; + + self.expressions.push(assignment); + } + + /// Attempt to infer a |> b(..c) as b(..c)(a) + fn infer_apply_to_call_pipe( + &mut self, + function: TypedExpr, + args: Vec>, + location: Span, + ) -> Result { + let (function, args, tipo) = self + .expr_typer + .do_infer_call_with_known_fun(function, args, location)?; + + let function = TypedExpr::Call { + location, + tipo, + args, + fun: Box::new(function), + }; + + let args = vec![self.untyped_left_hand_value_variable_call_argument()]; + // TODO: use `.with_unify_error_situation(UnifyErrorSituation::PipeTypeMismatch)` + // This will require the typing of the arguments to be lifted up out of + // the function below. If it is not we don't know if the error comes + // from incorrect usage of the pipe or if it originates from the + // argument expressions. + let (function, args, tipo) = self + .expr_typer + .do_infer_call_with_known_fun(function, args, location)?; + + Ok(TypedExpr::Call { + location, + tipo, + args, + fun: Box::new(function), + }) + } + + /// Attempt to infer a |> b(c) as b(a, c) + fn infer_insert_pipe( + &mut self, + function: TypedExpr, + mut arguments: Vec>, + location: Span, + ) -> Result { + arguments.insert(0, self.untyped_left_hand_value_variable_call_argument()); + // TODO: use `.with_unify_error_situation(UnifyErrorSituation::PipeTypeMismatch)` + // This will require the typing of the arguments to be lifted up out of + // the function below. If it is not we don't know if the error comes + // from incorrect usage of the pipe or if it originates from the + // argument expressions. + let (fun, args, tipo) = self + .expr_typer + .do_infer_call_with_known_fun(function, arguments, location)?; + + Ok(TypedExpr::Call { + location, + tipo, + args, + fun: Box::new(fun), + }) + } + + /// Attempt to infer a |> b as b(a) + fn infer_apply_pipe(&mut self, func: UntypedExpr) -> Result { + let func = Box::new(self.expr_typer.infer(func)?); + let return_type = self.expr_typer.new_unbound_var(); + + // Ensure that the function accepts one argument of the correct type + self.expr_typer + .environment + .unify( + func.tipo(), + function(vec![self.argument_type.clone()], return_type.clone()), + func.location(), + ) + .map_err(|e| { + let is_pipe_mismatch = self.check_if_pipe_type_mismatch(&e, func.location()); + + if is_pipe_mismatch { + e.with_unify_error_situation(UnifyErrorSituation::PipeTypeMismatch) + } else { + e + } + })?; + + Ok(TypedExpr::Call { + location: func.location(), + tipo: return_type, + fun: func, + args: vec![self.typed_left_hand_value_variable_call_argument()], + }) + } + + fn check_if_pipe_type_mismatch(&mut self, error: &Error, location: Span) -> bool { + let types = match error { + Error::CouldNotUnify { + expected, given, .. + } => (expected.as_ref(), given.as_ref()), + _ => return false, + }; + + match types { + (Type::Fn { args: a, .. }, Type::Fn { args: b, .. }) if a.len() == b.len() => { + match (a.get(0), b.get(0)) { + (Some(a), Some(b)) => self + .expr_typer + .environment + .unify(a.clone(), b.clone(), location) + .is_err(), + _ => false, + } + } + _ => false, + } + } +} diff --git a/examples/sample/lib/syntax.ak b/examples/sample/lib/syntax.ak index 2ea699aa..f111995a 100644 --- a/examples/sample/lib/syntax.ak +++ b/examples/sample/lib/syntax.ak @@ -1,10 +1,3 @@ -use aiken/builtins.{appendByteString} - -pub type Bool { - True - False -} - pub fn append(a: ByteArray, b: ByteArray) -> ByteArray { - appendByteString(a, b) + todo } \ No newline at end of file diff --git a/examples/sample/scripts/swap.ak b/examples/sample/scripts/swap.ak index b921bee5..ba4d7ae6 100644 --- a/examples/sample/scripts/swap.ak +++ b/examples/sample/scripts/swap.ak @@ -1,4 +1,4 @@ -use sample/syntax.{append, Bool} +use sample/syntax.{append} pub type Datum { something: String,