use std::fmt;
use codespan_reporting::diagnostic::Diagnostic;
use pretty::Arena;
use base::{
ast,
error::AsDiagnostic,
pos::{self, BytePos, Spanned},
source::FileId,
types::{ArcType, AsId, Filter, ToDoc, TypeExt, TypeFormatter},
};
use crate::{
implicits,
kindcheck::{self, Error as KindCheckError, KindError},
unify::Error as UnifyError,
unify_type::{self, Error as UnifyTypeError},
};
#[derive(Debug, Eq, PartialEq, Clone, Hash, Functor)]
pub enum TypeError<I, T> {
UndefinedVariable(I),
NotAFunction(T),
UndefinedType(I),
UndefinedField(T, I),
PatternError {
constructor_type: T,
pattern_args: usize,
},
Unification(T, T, Vec<UnifyTypeError<I, T>>),
KindError(KindCheckError<I, T>),
RecursionCheck(crate::recursion_check::Error),
DuplicateTypeDefinition(I),
DuplicateField(I),
InvalidProjection(T),
UndefinedRecord {
fields: Vec<I>,
},
EmptyCase,
Message(String),
UnableToResolveImplicit(implicits::Error<T>),
TypeConstructorReturnsWrongType {
expected: I,
actual: T,
},
}
impl<I, T> From<KindCheckError<I, T>> for TypeError<I, T> {
fn from(e: KindCheckError<I, T>) -> Self {
match e {
UnifyError::Other(KindError::UndefinedType(name)) => TypeError::UndefinedType(name),
UnifyError::Other(KindError::UndefinedField(typ, name)) => {
TypeError::UndefinedField(typ, name)
}
e => TypeError::KindError(e),
}
}
}
impl<I, T> From<implicits::Error<T>> for TypeError<I, T> {
fn from(e: implicits::Error<T>) -> Self {
TypeError::UnableToResolveImplicit(e)
}
}
impl<I, T> From<crate::recursion_check::Error> for TypeError<I, T> {
fn from(e: crate::recursion_check::Error) -> Self {
TypeError::RecursionCheck(e)
}
}
impl<I, T> fmt::Display for TypeError<I, T>
where
I: fmt::Display + AsRef<str> + Clone,
T::SpannedId: AsRef<str> + AsId<I>,
T: TypeExt<Id = I>
+ fmt::Display
+ ast::HasMetadata
+ pos::HasSpan
+ for<'a> ToDoc<'a, Arena<'a, ()>, (), ()>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::TypeError::*;
use pretty::DocAllocator;
match &*self {
UndefinedVariable(name) => write!(f, "Undefined variable `{}`", name),
NotAFunction(typ) => write!(f, "`{}` is not a function", typ),
UndefinedType(name) => write!(f, "Type `{}` is not defined", name),
UndefinedField(typ, field) => {
let fields = [field.clone()];
let filter = unify_type::similarity_filter(typ, &fields);
let arena = Arena::<()>::new();
write!(
f,
"Type `{}` does not have the field `{}`",
TypeFormatter::new(typ)
.filter(&*filter)
.pretty(&arena)
.1
.pretty(80),
field
)?;
Ok(())
}
Unification(expected, actual, errors) => {
let filters = errors
.iter()
.filter_map(|err| match err {
UnifyError::Other(err) => Some(err.make_filter()),
_ => None,
})
.collect::<Vec<_>>();
let filter = move |field: &I| {
if filters.is_empty() {
Filter::Retain
} else {
filters
.iter()
.fold(Filter::Drop, move |filter, f| match filter {
Filter::Retain => filter,
_ => match f(field) {
Filter::Drop => filter,
Filter::RetainKey => Filter::RetainKey,
Filter::Retain => Filter::Retain,
},
})
}
};
let arena = Arena::<()>::new();
let types = chain![&arena,
"Expected:",
chain![&arena,
arena.space(),
TypeFormatter::new(expected).filter(&filter).pretty(&arena)
].nest(4).group(),
arena.hardline(),
"Found:",
chain![&arena,
arena.space(),
TypeFormatter::new(actual).filter(&filter).pretty(&arena)
].nest(4).group()
]
.group();
let doc = chain![&arena,
"Expected the following types to be equal",
arena.hardline(),
types,
arena.hardline(),
arena.as_string(errors.len()),
" errors were found during unification:"
];
writeln!(f, "{}", doc.1.pretty(80))?;
if errors.is_empty() {
return Ok(());
}
for error in &errors[..errors.len() - 1] {
match error {
UnifyError::Other(err) => {
err.filter_fmt(&filter, f)?;
writeln!(f)?;
}
_ => writeln!(f, "{}", error)?,
}
}
write!(f, "{}", errors.last().unwrap())
}
PatternError { constructor_type, pattern_args } => {
write!(
f,
"Matching on constructor `{}` requires `{}` arguments but the pattern specifies `{}`",
constructor_type,
constructor_type.arg_iter().count(),
pattern_args
)
}
KindError(err) => kindcheck::fmt_kind_error(err, f),
RecursionCheck(err) => write!(f, "{}", err),
DuplicateTypeDefinition(id) => write!(
f,
"Type '{}' has been already been defined in this module",
id
),
DuplicateField(id) => write!(f, "The record has more than one field named '{}'", id),
InvalidProjection(typ) => write!(
f,
"Type '{}' is not a type which allows field accesses",
typ
),
UndefinedRecord { fields } => {
write!(f, "No type found with the following fields: ")?;
write!(f, "{}", fields[0])?;
for field in &fields[1..] {
write!(f, ", {}", field)?;
}
Ok(())
}
EmptyCase => write!(f, "`case` expression with no alternatives"),
Message(msg) => write!(f, "{}", msg),
UnableToResolveImplicit(err) => write!(f, "{}", err),
TypeConstructorReturnsWrongType { expected, actual } => write!(
f,
"The constructor returns the type `{}` instead of the expected type `{}`",
actual, expected
),
}
}
}
impl<I, T> AsDiagnostic for TypeError<I, T>
where
I: fmt::Display + AsRef<str> + Clone,
T::SpannedId: fmt::Display + AsRef<str> + AsId<I> + Clone,
T: TypeExt<Id = I>
+ fmt::Display
+ ast::HasMetadata
+ pos::HasSpan
+ for<'a> ToDoc<'a, Arena<'a, ()>, (), ()>,
{
fn as_diagnostic(&self, map: &base::source::CodeMap) -> Diagnostic<FileId> {
use self::TypeError::*;
match *self {
UnableToResolveImplicit(ref err) => err.as_diagnostic(map),
_ => Diagnostic::error().with_message(self.to_string()),
}
}
}
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub enum Help {
UndefinedFlatMapInDo,
ExtraArgument(u32, u32),
}
impl fmt::Display for Help {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Help::UndefinedFlatMapInDo => write!(
f,
"Try bringing the `flat_map` function found in the `Monad` \
instance for your type into scope"
),
Help::ExtraArgument(expected, actual) => {
if expected == 0 {
write!(f, "Attempted to call a non-function value")
} else {
write!(
f,
"Attempted to call function with {} argument{} but its type only has {}",
actual,
if actual == 1 { "" } else { "s" },
expected,
)
}
}
}
}
}
pub type HelpError<Id, T = ArcType<Id>> = crate::base::error::Help<TypeError<Id, T>, Help>;
pub type SpannedTypeError<Id, T = ArcType<Id>> = Spanned<HelpError<Id, T>, BytePos>;