arithmetic_typing/error/
op_errors.rs

1//! `OpErrors` type.
2
3use core::ops;
4
5use arithmetic_parser::{grammars::Grammar, Destructure, Spanned, SpannedExpr, SpannedLvalue};
6
7use crate::{
8    alloc::Vec,
9    ast::TypeAst,
10    error::{Error, ErrorContext, ErrorKind, ErrorPathFragment},
11    PrimitiveType,
12};
13
14/// Error container tied to a particular top-level operation that has a certain span
15/// and [context](ErrorContext).
16///
17/// Supplied as an argument to [`TypeArithmetic`] methods and [`Substitutions::unify()`].
18///
19/// [`TypeArithmetic`]: crate::arith::TypeArithmetic
20/// [`Substitutions::unify()`]: crate::arith::Substitutions::unify()
21#[derive(Debug)]
22pub struct OpErrors<'a, Prim: PrimitiveType> {
23    errors: Goat<'a, Vec<ErrorPrecursor<Prim>>>,
24    current_path: Vec<ErrorPathFragment>,
25}
26
27impl<Prim: PrimitiveType> OpErrors<'_, Prim> {
28    /// Adds a new `error` into this the error list.
29    pub fn push(&mut self, kind: ErrorKind<Prim>) {
30        self.errors.push(ErrorPrecursor {
31            kind,
32            path: self.current_path.clone(),
33        });
34    }
35
36    /// Invokes the provided closure and returns `false` if new errors were
37    /// added during the closure execution.
38    pub fn check(&mut self, check: impl FnOnce(OpErrors<'_, Prim>)) -> bool {
39        let error_count = self.errors.len();
40        check(self.by_ref());
41        self.errors.len() == error_count
42    }
43
44    /// Mutably borrows this container allowing to use it multiple times.
45    pub fn by_ref(&mut self) -> OpErrors<'_, Prim> {
46        OpErrors {
47            errors: Goat::Borrowed(&mut *self.errors),
48            current_path: self.current_path.clone(),
49        }
50    }
51
52    /// Narrows down the path to the error.
53    pub fn join_path(&mut self, path: impl Into<ErrorPathFragment>) -> OpErrors<'_, Prim> {
54        let mut current_path = self.current_path.clone();
55        current_path.push(path.into());
56        OpErrors {
57            errors: Goat::Borrowed(&mut *self.errors),
58            current_path,
59        }
60    }
61
62    pub(crate) fn push_path_fragment(&mut self, path: impl Into<ErrorPathFragment>) {
63        self.current_path.push(path.into());
64    }
65
66    pub(crate) fn pop_path_fragment(&mut self) {
67        self.current_path.pop().expect("Location is empty");
68    }
69
70    #[cfg(test)]
71    pub(crate) fn into_vec(self) -> Vec<ErrorKind<Prim>> {
72        let Goat::Owned(errors) = self.errors else {
73            panic!("Attempt to call `into_vec` for borrowed errors");
74        };
75        errors.into_iter().map(|err| err.kind).collect()
76    }
77}
78
79impl<Prim: PrimitiveType> OpErrors<'static, Prim> {
80    pub(crate) fn new() -> Self {
81        Self {
82            errors: Goat::Owned(Vec::new()),
83            current_path: Vec::new(),
84        }
85    }
86
87    pub(crate) fn contextualize<T: Grammar>(
88        self,
89        span: &SpannedExpr<'_, T>,
90        context: impl Into<ErrorContext<Prim>>,
91    ) -> Vec<Error<Prim>> {
92        let context = context.into();
93        self.do_contextualize(|item| item.into_expr_error(context.clone(), span))
94    }
95
96    fn do_contextualize(
97        self,
98        map_fn: impl Fn(ErrorPrecursor<Prim>) -> Error<Prim>,
99    ) -> Vec<Error<Prim>> {
100        let Goat::Owned(errors) = self.errors else {
101            unreachable!()
102        };
103        errors.into_iter().map(map_fn).collect()
104    }
105
106    pub(crate) fn contextualize_assignment<'a>(
107        self,
108        span: &SpannedLvalue<'a, TypeAst<'a>>,
109        context: &ErrorContext<Prim>,
110    ) -> Vec<Error<Prim>> {
111        if self.errors.is_empty() {
112            Vec::new()
113        } else {
114            self.do_contextualize(|item| item.into_assignment_error(context.clone(), span))
115        }
116    }
117
118    pub(crate) fn contextualize_destructure<'a>(
119        self,
120        span: &Spanned<'a, Destructure<'a, TypeAst<'a>>>,
121        create_context: impl FnOnce() -> ErrorContext<Prim>,
122    ) -> Vec<Error<Prim>> {
123        if self.errors.is_empty() {
124            Vec::new()
125        } else {
126            let context = create_context();
127            self.do_contextualize(|item| item.into_destructure_error(context.clone(), span))
128        }
129    }
130}
131
132/// Analogue of `Cow` with a mutable ref.
133#[derive(Debug)]
134enum Goat<'a, T> {
135    Owned(T),
136    Borrowed(&'a mut T),
137}
138
139impl<T> ops::Deref for Goat<'_, T> {
140    type Target = T;
141
142    fn deref(&self) -> &Self::Target {
143        match self {
144            Self::Owned(value) => value,
145            Self::Borrowed(mut_ref) => mut_ref,
146        }
147    }
148}
149
150impl<T> ops::DerefMut for Goat<'_, T> {
151    fn deref_mut(&mut self) -> &mut Self::Target {
152        match self {
153            Self::Owned(value) => value,
154            Self::Borrowed(mut_ref) => mut_ref,
155        }
156    }
157}
158
159#[derive(Debug)]
160struct ErrorPrecursor<Prim: PrimitiveType> {
161    kind: ErrorKind<Prim>,
162    path: Vec<ErrorPathFragment>,
163}
164
165impl<Prim: PrimitiveType> ErrorPrecursor<Prim> {
166    fn into_expr_error<T: Grammar>(
167        self,
168        context: ErrorContext<Prim>,
169        root_expr: &SpannedExpr<'_, T>,
170    ) -> Error<Prim> {
171        Error {
172            inner: ErrorPathFragment::walk_expr(&self.path, root_expr)
173                .copy_with_extra(self.kind)
174                .into(),
175            root_location: root_expr.with_no_extra().into(),
176            context,
177            path: self.path,
178        }
179    }
180
181    fn into_assignment_error<'a>(
182        self,
183        context: ErrorContext<Prim>,
184        root_lvalue: &SpannedLvalue<'a, TypeAst<'a>>,
185    ) -> Error<Prim> {
186        Error {
187            inner: ErrorPathFragment::walk_lvalue(&self.path, root_lvalue)
188                .copy_with_extra(self.kind)
189                .into(),
190            root_location: root_lvalue.with_no_extra().into(),
191            context,
192            path: self.path,
193        }
194    }
195
196    fn into_destructure_error<'a>(
197        self,
198        context: ErrorContext<Prim>,
199        root_destructure: &Spanned<'a, Destructure<'a, TypeAst<'a>>>,
200    ) -> Error<Prim> {
201        Error {
202            inner: ErrorPathFragment::walk_destructure(&self.path, root_destructure)
203                .copy_with_extra(self.kind)
204                .into(),
205            root_location: root_destructure.with_no_extra().into(),
206            context,
207            path: self.path,
208        }
209    }
210}