arithmetic_typing/error/
mod.rs

1//! Errors related to type inference.
2
3use core::fmt;
4
5use arithmetic_parser::{Location, Spanned, UnsupportedType};
6
7pub use self::{
8    kind::{ErrorKind, TupleContext},
9    op_errors::OpErrors,
10    path::ErrorPathFragment,
11};
12use crate::{
13    alloc::{vec, ToOwned, Vec},
14    arith::{BinaryOpContext, UnaryOpContext},
15    ast::AstConversionError,
16    visit::VisitMut,
17    PrimitiveType, Tuple, Type,
18};
19
20mod kind;
21mod op_errors;
22mod path;
23
24/// Type error together with the corresponding code span.
25#[derive(Debug, Clone)]
26pub struct Error<Prim: PrimitiveType> {
27    inner: Location<ErrorKind<Prim>>,
28    root_location: Location,
29    context: ErrorContext<Prim>,
30    path: Vec<ErrorPathFragment>,
31}
32
33impl<Prim: PrimitiveType> fmt::Display for Error<Prim> {
34    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35        write!(
36            formatter,
37            "{}:{}: {}",
38            self.main_location().location_line(),
39            self.main_location().get_column(),
40            self.kind()
41        )
42    }
43}
44
45#[cfg(feature = "std")]
46impl<Prim: PrimitiveType> std::error::Error for Error<Prim> {
47    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
48        Some(self.kind())
49    }
50}
51
52impl<Prim: PrimitiveType> Error<Prim> {
53    pub(crate) fn unsupported<T>(
54        unsupported: impl Into<UnsupportedType>,
55        span: &Spanned<'_, T>,
56    ) -> Self {
57        let kind = ErrorKind::unsupported(unsupported);
58        Self {
59            inner: span.copy_with_extra(kind).into(),
60            root_location: span.with_no_extra().into(),
61            context: ErrorContext::None,
62            path: Vec::new(),
63        }
64    }
65
66    pub(crate) fn undefined_var<T>(span: &Spanned<'_, T>) -> Self {
67        let ident = (*span.fragment()).to_owned();
68        Self {
69            inner: span.copy_with_extra(ErrorKind::UndefinedVar(ident)).into(),
70            root_location: span.with_no_extra().into(),
71            context: ErrorContext::None,
72            path: Vec::new(),
73        }
74    }
75
76    pub(crate) fn repeated_assignment(span: Spanned<'_>) -> Self {
77        let ident = (*span.fragment()).to_owned();
78        Self {
79            inner: span
80                .copy_with_extra(ErrorKind::RepeatedAssignment(ident))
81                .into(),
82            root_location: span.with_no_extra().into(),
83            context: ErrorContext::None,
84            path: Vec::new(),
85        }
86    }
87
88    pub(crate) fn repeated_field(span: Spanned<'_>) -> Self {
89        let ident = (*span.fragment()).to_owned();
90        Self {
91            inner: span.copy_with_extra(ErrorKind::RepeatedField(ident)).into(),
92            root_location: span.with_no_extra().into(),
93            context: ErrorContext::None,
94            path: Vec::new(),
95        }
96    }
97
98    pub(crate) fn conversion<T>(kind: AstConversionError, span: &Spanned<'_, T>) -> Self {
99        let kind = ErrorKind::AstConversion(kind);
100        Self {
101            inner: span.copy_with_extra(kind).into(),
102            root_location: span.with_no_extra().into(),
103            context: ErrorContext::None,
104            path: Vec::new(),
105        }
106    }
107
108    pub(crate) fn invalid_field_name(span: Spanned<'_>) -> Self {
109        let ident = (*span.fragment()).to_owned();
110        Self {
111            inner: span
112                .copy_with_extra(ErrorKind::InvalidFieldName(ident))
113                .into(),
114            root_location: span.into(),
115            context: ErrorContext::None,
116            path: Vec::new(),
117        }
118    }
119
120    pub(crate) fn index_out_of_bounds<T>(
121        receiver: Tuple<Prim>,
122        span: &Spanned<'_, T>,
123        index: usize,
124    ) -> Self {
125        Self {
126            inner: span
127                .copy_with_extra(ErrorKind::IndexOutOfBounds {
128                    index,
129                    len: receiver.len(),
130                })
131                .into(),
132            root_location: span.with_no_extra().into(),
133            context: ErrorContext::TupleIndex {
134                ty: Type::Tuple(receiver),
135            },
136            path: Vec::new(),
137        }
138    }
139
140    pub(crate) fn cannot_index<T>(receiver: Type<Prim>, span: &Spanned<'_, T>) -> Self {
141        Self {
142            inner: span.copy_with_extra(ErrorKind::CannotIndex).into(),
143            root_location: span.with_no_extra().into(),
144            context: ErrorContext::TupleIndex { ty: receiver },
145            path: Vec::new(),
146        }
147    }
148
149    pub(crate) fn unsupported_index<T>(receiver: Type<Prim>, span: &Spanned<'_, T>) -> Self {
150        Self {
151            inner: span.copy_with_extra(ErrorKind::UnsupportedIndex).into(),
152            root_location: span.with_no_extra().into(),
153            context: ErrorContext::TupleIndex { ty: receiver },
154            path: Vec::new(),
155        }
156    }
157
158    /// Gets the kind of this error.
159    pub fn kind(&self) -> &ErrorKind<Prim> {
160        &self.inner.extra
161    }
162
163    /// Gets the most specific code span of this error.
164    pub fn main_location(&self) -> Location {
165        self.inner.with_no_extra()
166    }
167
168    /// Gets the root code location of the failed operation. May coincide with [`Self::main_location()`].
169    pub fn root_location(&self) -> Location {
170        self.root_location
171    }
172
173    /// Gets the context for an operation that has failed.
174    pub fn context(&self) -> &ErrorContext<Prim> {
175        &self.context
176    }
177
178    /// Gets the path of this error relative to the failed top-level operation.
179    /// This can be used for highlighting relevant parts of types in [`Self::context()`].
180    pub fn path(&self) -> &[ErrorPathFragment] {
181        &self.path
182    }
183}
184
185/// List of [`Error`]s.
186///
187/// # Examples
188///
189/// ```
190/// # use arithmetic_parser::grammars::{F32Grammar, Parse};
191/// # use arithmetic_typing::{defs::Prelude, error::Errors, Annotated, TypeEnvironment};
192/// # use std::collections::HashSet;
193/// # fn main() -> anyhow::Result<()> {
194/// let buggy_code = Annotated::<F32Grammar>::parse_statements(r#"
195///     numbers: ['T; _] = (1, 2, 3);
196///     numbers.filter(|x| x, 1)
197/// "#)?;
198/// let mut env: TypeEnvironment = Prelude::iter().collect();
199/// let errors: Errors<_> = env.process_statements(&buggy_code).unwrap_err();
200/// assert_eq!(errors.len(), 3);
201///
202/// let messages: HashSet<_> = errors.iter().map(ToString::to_string).collect();
203/// assert!(messages
204///     .iter()
205///     .any(|msg| msg.contains("Type param `T` is not scoped by function definition")));
206/// assert!(messages
207///     .contains("3:20: Type `Num` is not assignable to type `Bool`"));
208/// assert!(messages
209///     .contains("3:5: Function expects 2 args, but is called with 3 args"));
210/// # Ok(())
211/// # }
212/// ```
213#[derive(Debug, Clone)]
214pub struct Errors<Prim: PrimitiveType> {
215    inner: Vec<Error<Prim>>,
216    first_failing_statement: usize,
217}
218
219impl<Prim: PrimitiveType> Errors<Prim> {
220    pub(crate) fn new() -> Self {
221        Self {
222            inner: Vec::new(),
223            first_failing_statement: 0,
224        }
225    }
226
227    pub(crate) fn push(&mut self, err: Error<Prim>) {
228        self.inner.push(err);
229    }
230
231    pub(crate) fn extend(&mut self, errors: Vec<Error<Prim>>) {
232        self.inner.extend(errors);
233    }
234
235    /// Returns the number of errors in this list.
236    pub fn len(&self) -> usize {
237        self.inner.len()
238    }
239
240    /// Checks if this list is empty (there are no errors).
241    pub fn is_empty(&self) -> bool {
242        self.inner.is_empty()
243    }
244
245    /// Iterates over errors contained in this list.
246    pub fn iter(&self) -> impl Iterator<Item = &Error<Prim>> + '_ {
247        self.inner.iter()
248    }
249
250    /// Returns the index of the first failing statement within a `Block` that has errored.
251    /// If the error is in the return value, this index will be equal to the number of statements
252    /// in the block.
253    pub fn first_failing_statement(&self) -> usize {
254        self.first_failing_statement
255    }
256
257    pub(crate) fn set_first_failing_statement(&mut self, index: usize) {
258        self.first_failing_statement = index;
259    }
260
261    /// Post-processes these errors, resolving the contained `Type`s using
262    /// the provided `type_resolver`.
263    pub(crate) fn post_process(&mut self, type_resolver: &mut impl VisitMut<Prim>) {
264        for error in &mut self.inner {
265            error.context.map_types(type_resolver);
266        }
267    }
268}
269
270impl<Prim: PrimitiveType> fmt::Display for Errors<Prim> {
271    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
272        for (i, error) in self.inner.iter().enumerate() {
273            write!(formatter, "{error}")?;
274            if i + 1 < self.inner.len() {
275                formatter.write_str("\n")?;
276            }
277        }
278        Ok(())
279    }
280}
281
282#[cfg(feature = "std")]
283impl<Prim: PrimitiveType> std::error::Error for Errors<Prim> {}
284
285impl<Prim: PrimitiveType> IntoIterator for Errors<Prim> {
286    type Item = Error<Prim>;
287    type IntoIter = vec::IntoIter<Self::Item>;
288
289    fn into_iter(self) -> Self::IntoIter {
290        self.inner.into_iter()
291    }
292}
293
294/// Context of a [`Error`] corresponding to a top-level operation that has errored.
295/// Generally, contains resolved types concerning the operation, such as operands of
296/// a binary arithmetic op.
297#[derive(Debug, Clone)]
298#[non_exhaustive]
299pub enum ErrorContext<Prim: PrimitiveType> {
300    /// No context.
301    None,
302    /// Processing lvalue (before assignment).
303    Lvalue(Type<Prim>),
304    /// Function definition.
305    FnDefinition {
306        /// Types of function arguments.
307        args: Tuple<Prim>,
308    },
309    /// Function call.
310    FnCall {
311        /// Function definition. Note that this is not necessarily a [`Function`](crate::Function).
312        definition: Type<Prim>,
313        /// Signature of the call.
314        call_signature: Type<Prim>,
315    },
316    /// Assignment.
317    Assignment {
318        /// Left-hand side of the assignment.
319        lhs: Type<Prim>,
320        /// Right-hand side of the assignment.
321        rhs: Type<Prim>,
322    },
323    /// Type cast.
324    TypeCast {
325        /// Source type of the casted value.
326        source: Type<Prim>,
327        /// Target type of the cast.
328        target: Type<Prim>,
329    },
330    /// Unary operation.
331    UnaryOp(UnaryOpContext<Prim>),
332    /// Binary operation.
333    BinaryOp(BinaryOpContext<Prim>),
334    /// Tuple indexing operation.
335    TupleIndex {
336        /// Type being indexed.
337        ty: Type<Prim>,
338    },
339    /// Field access for an object.
340    ObjectFieldAccess {
341        /// Type being accessed.
342        ty: Type<Prim>,
343    },
344}
345
346impl<Prim: PrimitiveType> From<UnaryOpContext<Prim>> for ErrorContext<Prim> {
347    fn from(value: UnaryOpContext<Prim>) -> Self {
348        Self::UnaryOp(value)
349    }
350}
351
352impl<Prim: PrimitiveType> From<BinaryOpContext<Prim>> for ErrorContext<Prim> {
353    fn from(value: BinaryOpContext<Prim>) -> Self {
354        Self::BinaryOp(value)
355    }
356}
357
358impl<Prim: PrimitiveType> ErrorContext<Prim> {
359    fn map_types(&mut self, mapper: &mut impl VisitMut<Prim>) {
360        match self {
361            Self::None => { /* Do nothing. */ }
362            Self::Lvalue(lvalue) => mapper.visit_type_mut(lvalue),
363            Self::FnDefinition { args } => mapper.visit_tuple_mut(args),
364            Self::FnCall {
365                definition,
366                call_signature,
367            } => {
368                mapper.visit_type_mut(definition);
369                mapper.visit_type_mut(call_signature);
370            }
371            Self::Assignment { lhs, rhs } | Self::BinaryOp(BinaryOpContext { lhs, rhs, .. }) => {
372                mapper.visit_type_mut(lhs);
373                mapper.visit_type_mut(rhs);
374            }
375            Self::TypeCast { source, target } => {
376                mapper.visit_type_mut(source);
377                mapper.visit_type_mut(target);
378            }
379            Self::UnaryOp(UnaryOpContext { arg, .. }) => {
380                mapper.visit_type_mut(arg);
381            }
382            Self::TupleIndex { ty } | Self::ObjectFieldAccess { ty } => {
383                mapper.visit_type_mut(ty);
384            }
385        }
386    }
387}