feat: typechecking is working

This commit is contained in:
rvcas 2022-10-21 23:31:48 -04:00 committed by Lucas
parent cabc653167
commit 5244e58c9f
15 changed files with 2824 additions and 179 deletions

View File

@ -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<Self>),
#[error("failed to parse")]
#[error("parsing")]
Parse {
path: PathBuf,
@ -37,10 +37,11 @@ pub enum Error {
error: Box<ParseError>,
},
#[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<Box<dyn Display + 'a>> {
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<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
match self {
Warning::Type { warning, .. } => warning.labels(),
}
}
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
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(())
}
}

View File

@ -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());
};
}

View File

@ -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<String, PathBuf>,

View File

@ -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<T, Expr, ConstantRecordTag, PackageName> {
},
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DefinitionLocation<'module> {
pub module: Option<&'module str>,
pub span: Span,
}
pub type TypedConstant = Constant<Arc<Type>, String>;
pub type UntypedConstant = Constant<(), ()>;
@ -200,6 +206,8 @@ impl<A, B> Constant<A, B> {
}
}
pub type TypedCallArg = CallArg<TypedExpr>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallArg<A> {
pub label: Option<String>,
@ -453,11 +461,6 @@ pub enum Pattern<Constructor, Type> {
value: String,
},
Float {
location: Span,
value: String,
},
String {
location: Span,
value: String,
@ -509,11 +512,34 @@ pub enum Pattern<Constructor, Type> {
with_spread: bool,
tipo: Type,
},
// Tuple {
// location: Span,
// elems: Vec<Self>,
// },
}
Tuple {
location: Span,
elems: Vec<Self>,
},
impl<A, B> Pattern<A, B> {
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<PatternConstructor, Arc<Type>>;
pub type TypedClause = Clause<TypedExpr, PatternConstructor, Arc<Type>, String>;
pub type UntypedClause = Clause<UntypedExpr, (), (), ()>;
#[derive(Debug, Clone, PartialEq)]
@ -540,6 +565,23 @@ pub struct Clause<Expr, PatternConstructor, Type, RecordTag> {
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<Arc<Type>, String>;
#[derive(Debug, Clone, PartialEq)]
pub enum ClauseGuard<Type, RecordTag> {
Equals {
@ -596,16 +638,52 @@ pub enum ClauseGuard<Type, RecordTag> {
name: String,
},
TupleIndex {
location: Span,
index: u64,
tipo: Type,
tuple: Box<Self>,
},
// TupleIndex {
// location: Span,
// index: u64,
// tipo: Type,
// tuple: Box<Self>,
// },
Constant(Constant<Type, RecordTag>),
}
impl<A, B> ClauseGuard<A, B> {
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<Type> {
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<TypedExpr>;
pub type UntypedIfBranch = IfBranch<UntypedExpr>;
@ -616,7 +694,7 @@ pub struct IfBranch<Expr> {
pub location: Span,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct TypedRecordUpdateArg {
pub label: String,
pub location: Span,

View File

@ -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<Type>,
elems: Vec<Self>,
},
TupleIndex {
location: Span,
tipo: Arc<Type>,
index: u64,
tuple: Box<Self>,
},
// Tuple {
// location: Span,
// tipo: Arc<Type>,
// elems: Vec<Self>,
// },
// TupleIndex {
// location: Span,
// tipo: Arc<Type>,
// index: u64,
// tuple: Box<Self>,
// },
Todo {
location: Span,
label: Option<String>,
@ -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<DefinitionLocation<'_>> {
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<Self>,
},
Tuple {
location: Span,
elems: Vec<Self>,
},
TupleIndex {
location: Span,
index: u64,
tuple: Box<Self>,
},
// Tuple {
// location: Span,
// elems: Vec<Self>,
// },
// TupleIndex {
// location: Span,
// index: u64,
// tuple: Box<Self>,
// },
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, .. }

View File

@ -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<Type>,
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<Type>,
}
#[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,
}
}
}

View File

@ -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<Pattern<PatternConstructor, Arc<Type>>>,
value_typ: Arc<Type>,
location: Span,
) -> Result<(), Vec<String>> {
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<String> =
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<String>,
name: &str,
location: Span,
) -> Result<&Vec<String>, 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<A>(args: &[CallArg<A>]) -> Result<(),
Ok(())
}
pub(super) fn collapse_links(t: Arc<Type>) -> Arc<Type> {
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<A>(

View File

@ -69,6 +69,14 @@ pub enum Error {
labels: Vec<String>,
},
#[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<String>,
},
#[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<String>,
@ -164,6 +192,16 @@ pub enum Error {
type_constructors: Vec<String>,
},
#[error("unknown record field {label}")]
UnknownRecordField {
#[label]
location: Span,
typ: Arc<Type>,
label: String,
fields: Vec<String>,
situation: Option<UnknownRecordFieldSituation>,
},
#[error("unknown type {name}")]
UnknownType {
#[label]
@ -180,6 +218,19 @@ pub enum Error {
variables: Vec<String>,
},
#[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<u64, String>) -> 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<Type>,
tipo: Arc<Type>,
},
#[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,
}

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,12 @@ pub struct ScopeResetData {
rigid_type_names: HashMap<u64, String>,
}
impl Default for Hydrator {
fn default() -> Self {
Self::new()
}
}
impl Hydrator {
pub fn new() -> Hydrator {
Hydrator {

View File

@ -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),

View File

@ -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<String>,
}
enum PatternMode {
Initial,
Alternative(Vec<String>),
}
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<Type>,
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<Type>],
location: &Span,
) -> Result<Vec<TypedPattern>, 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<Type>],
location: &Span,
) -> Result<Vec<TypedPattern>, 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<UntypedPatternBitStringSegment>,
// location: Span,
// ) -> Result<TypedPattern, Error> {
// 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<TypedPatternBitStringSegment, Error> {
// 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<Type>,
) -> Result<TypedPattern, Error> {
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.",),
}
}
}
}
}

View File

@ -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<Type>,
argument_location: Span,
location: Span,
expressions: Vec<TypedExpr>,
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<UntypedExpr>,
) -> Result<TypedExpr, Error> {
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<Item = UntypedExpr>,
) -> Result<TypedExpr, Error> {
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<Item = UntypedExpr>,
) -> 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<TypedExpr> {
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<UntypedExpr> {
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<CallArg<UntypedExpr>>,
location: Span,
) -> Result<TypedExpr, Error> {
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<CallArg<UntypedExpr>>,
location: Span,
) -> Result<TypedExpr, Error> {
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<TypedExpr, Error> {
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,
}
}
}

View File

@ -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
}

View File

@ -1,4 +1,4 @@
use sample/syntax.{append, Bool}
use sample/syntax.{append}
pub type Datum {
something: String,