1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
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},
};

/// Type representing a single error when checking a type
#[derive(Debug, Eq, PartialEq, Clone, Hash, Functor)]
pub enum TypeError<I, T> {
    /// Variable has not been defined before it was used
    UndefinedVariable(I),
    /// Attempt to call a type which is not a function
    NotAFunction(T),
    /// Type has not been defined before it was used
    UndefinedType(I),
    /// Type were expected to have a certain field
    UndefinedField(T, I),
    /// Constructor type was found in a pattern but did not have the expected number of arguments
    PatternError {
        constructor_type: T,
        pattern_args: usize,
    },
    /// Errors found when trying to unify two types
    Unification(T, T, Vec<UnifyTypeError<I, T>>),
    /// Error were found when trying to unify the kinds of two types
    KindError(KindCheckError<I, T>),
    /// Error were found when checking value recursion
    RecursionCheck(crate::recursion_check::Error),
    /// Multiple types were declared with the same name in the same expression
    DuplicateTypeDefinition(I),
    /// A field was defined more than once in a record constructor or pattern match
    DuplicateField(I),
    /// Type is not a type which has any fields
    InvalidProjection(T),
    /// Expected to find a record with the following fields
    UndefinedRecord {
        fields: Vec<I>,
    },
    /// Found a case expression without any alternatives
    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>;