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
//! `ErrorKind` and tightly related types.

use core::fmt;

use arithmetic_parser::UnsupportedType;

use crate::{
    alloc::{Box, HashSet, String},
    arith::Constraint,
    ast::AstConversionError,
    error::ErrorPathFragment,
    PrimitiveType, TupleIndex, TupleLen, Type,
};

/// Context in which a tuple is used.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TupleContext {
    /// Generic tuple use: assignment, destructuring, or creating a tuple from elements.
    Generic,
    /// The tuple represents function arguments.
    FnArgs,
}

impl TupleContext {
    pub(crate) fn element(self, index: usize) -> ErrorPathFragment {
        let index = TupleIndex::Start(index);
        match self {
            Self::Generic => ErrorPathFragment::TupleElement(Some(index)),
            Self::FnArgs => ErrorPathFragment::FnArg(Some(index)),
        }
    }

    pub(crate) fn end_element(self, index: usize) -> ErrorPathFragment {
        let index = TupleIndex::End(index);
        match self {
            Self::Generic => ErrorPathFragment::TupleElement(Some(index)),
            Self::FnArgs => ErrorPathFragment::FnArg(Some(index)),
        }
    }
}

/// Kinds of errors that can occur during type inference.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ErrorKind<Prim: PrimitiveType> {
    /// Trying to unify incompatible types. The first type is LHS, the second one is RHS.
    TypeMismatch(Type<Prim>, Type<Prim>),
    /// Incompatible tuple lengths.
    TupleLenMismatch {
        /// Length of the LHS. This is the length determined by type annotations
        /// for assignments and the number of actually supplied args in function calls.
        lhs: TupleLen,
        /// Length of the RHS. This is usually the actual tuple length in assignments
        /// and the number of expected args in function calls.
        rhs: TupleLen,
        /// Context in which the error has occurred.
        context: TupleContext,
    },
    /// Undefined variable occurrence.
    UndefinedVar(String),
    /// Trying to unify a type with a type containing it.
    RecursiveType(Type<Prim>),

    /// Repeated assignment to the same variable in function args or tuple destructuring.
    RepeatedAssignment(String),

    /// Field name is invalid.
    InvalidFieldName(String),
    /// Value cannot be indexed (i.e., not a tuple).
    CannotIndex,
    /// Unsupported indexing operation. For example, the receiver type is not known,
    /// or it is a tuple with an unknown length, and the type of the element cannot be decided.
    UnsupportedIndex,
    /// Index is out of bounds for the indexed tuple.
    IndexOutOfBounds {
        /// Index.
        index: usize,
        /// Actual tuple length.
        len: TupleLen,
    },

    /// Repeated field in object initialization / destructuring.
    RepeatedField(String),
    /// Cannot access fields in a value (i.e., it's not an object).
    CannotAccessFields,
    /// Field set differs between LHS and RHS, which are both concrete objects.
    FieldsMismatch {
        /// Fields in LHS.
        lhs_fields: HashSet<String>,
        /// Fields in RHS.
        rhs_fields: HashSet<String>,
    },
    /// Concrete object does not have required fields.
    MissingFields {
        /// Missing fields.
        fields: HashSet<String>,
        /// Available object fields.
        available_fields: HashSet<String>,
    },

    /// Mention of a bounded type or length variable in a type supplied
    /// to [`Substitutions::unify()`].
    ///
    /// Bounded variables are instantiated into free vars automatically during
    /// type inference, so this error
    /// can only occur with types manually supplied to `Substitutions::unify()`.
    ///
    /// [`Substitutions::unify()`]: crate::arith::Substitutions::unify()
    UnresolvedParam,

    /// Failure when applying constraint to a type.
    FailedConstraint {
        /// Type that fails constraint requirement.
        ty: Type<Prim>,
        /// Failing constraint.
        constraint: Box<dyn Constraint<Prim>>,
    },
    /// Length with the static constraint is actually dynamic (contains [`UnknownLen::Dynamic`]).
    ///
    /// [`UnknownLen::Dynamic`]: crate::UnknownLen::Dynamic
    DynamicLen(TupleLen),

    /// Language feature not supported by type inference logic.
    UnsupportedFeature(UnsupportedType),

    /// Type not supported by type inference logic. For example,
    /// a [`TypeArithmetic`] or [`Constraint`] implementations may return this error
    /// if they encounter an unknown [`Type`] variant.
    ///
    /// [`TypeArithmetic`]: crate::arith::TypeArithmetic
    /// [`Constraint`]: crate::arith::Constraint
    UnsupportedType(Type<Prim>),

    /// Unsupported use of type or length params in a function declaration.
    ///
    /// Type or length params are currently not supported in type annotations. Here's an example
    /// of code that triggers this error:
    ///
    /// ```text
    /// identity: (('T,)) -> ('T,) = |x| x;
    /// ```
    UnsupportedParam,

    /// Error while instantiating a type from AST.
    AstConversion(AstConversionError),
}

impl<Prim: PrimitiveType> fmt::Display for ErrorKind<Prim> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::TypeMismatch(lhs, rhs) => write!(
                formatter,
                "Type `{rhs}` is not assignable to type `{lhs}`"
            ),
            Self::TupleLenMismatch {
                lhs,
                rhs,
                context: TupleContext::FnArgs,
            } => write!(
                formatter,
                "Function expects {lhs} args, but is called with {rhs} args"
            ),
            Self::TupleLenMismatch { lhs, rhs, .. } => write!(
                formatter,
                "Expected a tuple with {lhs} elements, got one with {rhs} elements"
            ),

            Self::UndefinedVar(name) => write!(formatter, "Variable `{name}` is not defined"),

            Self::RecursiveType(ty) => write!(
                formatter,
                "Cannot unify type 'T with a type containing it: {ty}"
            ),

            Self::RepeatedAssignment(name) => {
                write!(
                    formatter,
                    "Repeated assignment to the same variable `{name}`"
                )
            }

            Self::InvalidFieldName(name) => {
                write!(formatter, "`{name}` is not a valid field name")
            }
            Self::CannotIndex => formatter.write_str("Value cannot be indexed"),
            Self::UnsupportedIndex => formatter.write_str("Unsupported indexing operation"),
            Self::IndexOutOfBounds { index, len } => write!(
                formatter,
                "Attempting to get element {index} from tuple with length {len}"
            ),

            Self::RepeatedField(name) => write!(formatter, "Repeated object field `{name}`"),
            Self::CannotAccessFields => formatter.write_str("Value is not an object"),
            Self::FieldsMismatch {
                lhs_fields,
                rhs_fields,
            } => write!(
                formatter,
                "Cannot assign object with fields {rhs_fields:?} to object with fields {lhs_fields:?}"
            ),
            Self::MissingFields {
                fields,
                available_fields,
            } => write!(
                formatter,
                "Missing field(s) {fields:?} from object (available fields: {available_fields:?})"
            ),

            Self::UnresolvedParam => {
                formatter.write_str("Params not instantiated into variables cannot be unified")
            }

            Self::FailedConstraint { ty, constraint } => {
                write!(formatter, "Type `{ty}` fails constraint `{constraint}`")
            }
            Self::DynamicLen(len) => {
                write!(formatter, "Length `{len}` is required to be static")
            }

            Self::UnsupportedFeature(ty) => write!(formatter, "Unsupported {ty}"),
            Self::UnsupportedType(ty) => write!(formatter, "Unsupported type: {ty}"),
            Self::UnsupportedParam => {
                formatter.write_str("Params in declared function types are not supported yet")
            }

            Self::AstConversion(err) => write!(
                formatter,
                "Error instantiating type from annotation: {err}"
            ),
        }
    }
}

#[cfg(feature = "std")]
impl<Prim: PrimitiveType> std::error::Error for ErrorKind<Prim> {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::AstConversion(err) => Some(err),
            _ => None,
        }
    }
}

impl<Prim: PrimitiveType> ErrorKind<Prim> {
    /// Creates an error for an lvalue type not supported by the interpreter.
    pub fn unsupported<T: Into<UnsupportedType>>(ty: T) -> Self {
        Self::UnsupportedFeature(ty.into())
    }

    /// Creates a "failed constraint" error.
    pub fn failed_constraint(ty: Type<Prim>, constraint: impl Constraint<Prim> + Clone) -> Self {
        Self::FailedConstraint {
            ty,
            constraint: Box::new(constraint),
        }
    }
}