arithmetic_typing/error/
path.rs

1//! `ErrorLocation` and related functionality.
2
3use arithmetic_parser::{
4    grammars::Grammar, Destructure, DestructureRest, Expr, Lvalue, Spanned, SpannedExpr,
5    SpannedLvalue,
6};
7
8use crate::{
9    alloc::{String, ToOwned},
10    ast::{SpannedTypeAst, TupleAst, TypeAst},
11    TupleIndex,
12};
13
14impl TupleIndex {
15    fn get_from_tuple<'r, 'a>(self, tuple: &'r TupleAst<'a>) -> Option<&'r SpannedTypeAst<'a>> {
16        match self {
17            Self::Start(i) => tuple.start.get(i),
18            Self::Middle => tuple
19                .middle
20                .as_ref()
21                .map(|middle| middle.extra.element.as_ref()),
22            Self::End(i) => tuple.end.get(i),
23        }
24    }
25
26    fn get_from_destructure<'r, 'a>(
27        self,
28        destructure: &'r Destructure<'a, TypeAst<'a>>,
29    ) -> Option<SpannedLvalueTree<'r, 'a>> {
30        match self {
31            Self::Start(i) => destructure.start.get(i).map(LvalueTree::from_lvalue),
32            Self::Middle => destructure
33                .middle
34                .as_ref()
35                .and_then(|middle| match &middle.extra {
36                    DestructureRest::Named { ty: Some(ty), .. } => Some(LvalueTree::from_span(ty)),
37                    DestructureRest::Named { variable, .. } => {
38                        Some(LvalueTree::from_span(variable))
39                    }
40                    DestructureRest::Unnamed => None,
41                }),
42            Self::End(i) => destructure.end.get(i).map(LvalueTree::from_lvalue),
43        }
44    }
45}
46
47/// Fragment of a path for an [`Error`](crate::error::Error).
48#[derive(Debug, Clone, PartialEq, Eq)]
49#[non_exhaustive]
50pub enum ErrorPathFragment {
51    /// Function argument with the specified index (0-based; can be `None` if the error cannot
52    /// be attributed to a specific index).
53    FnArg(Option<TupleIndex>),
54    /// Function return type.
55    FnReturnType,
56    /// Tuple element with the specified index (0-based; can be `None` if the error cannot
57    /// be attributed to a specific index).
58    TupleElement(Option<TupleIndex>),
59    /// Object field with the specified name.
60    ObjectField(String),
61    /// Left-hand side of a binary operation.
62    Lhs,
63    /// Right-hand side of a binary operation.
64    Rhs,
65}
66
67impl From<TupleIndex> for ErrorPathFragment {
68    fn from(index: TupleIndex) -> Self {
69        Self::TupleElement(Some(index))
70    }
71}
72
73impl From<&str> for ErrorPathFragment {
74    fn from(field_name: &str) -> Self {
75        Self::ObjectField(field_name.to_owned())
76    }
77}
78
79impl ErrorPathFragment {
80    /// Walks the provided `expr` and returns the most exact span found in it.
81    pub(super) fn walk_expr<'a, T: Grammar>(
82        path: &[Self],
83        expr: &SpannedExpr<'a, T>,
84    ) -> Spanned<'a> {
85        let mut refined = Self::walk(path, expr, Self::step_into_expr);
86        while let Expr::TypeCast { value, .. } = &refined.extra {
87            refined = value.as_ref();
88        }
89        refined.with_no_extra()
90    }
91
92    fn walk<T: Copy>(mut path: &[Self], init: T, refine: impl Fn(&Self, T) -> Option<T>) -> T {
93        let mut refined = init;
94        while !path.is_empty() {
95            if let Some(refinement) = refine(&path[0], refined) {
96                refined = refinement;
97                path = &path[1..];
98            } else {
99                break;
100            }
101        }
102        refined
103    }
104
105    fn step_into_expr<'r, 'a, T: Grammar>(
106        &self,
107        mut expr: &'r SpannedExpr<'a, T>,
108    ) -> Option<&'r SpannedExpr<'a, T>> {
109        while let Expr::TypeCast { value, .. } = &expr.extra {
110            expr = value.as_ref();
111        }
112
113        match self {
114            // `TupleIndex::FromEnd` should not occur in this context.
115            Self::FnArg(Some(TupleIndex::Start(index))) => match &expr.extra {
116                Expr::Function { args, .. } => Some(&args[*index]),
117                Expr::Method { receiver, args, .. } => Some(if *index == 0 {
118                    receiver.as_ref()
119                } else {
120                    &args[*index - 1]
121                }),
122                _ => None,
123            },
124
125            Self::Lhs => {
126                if let Expr::Binary { lhs, .. } = &expr.extra {
127                    Some(lhs.as_ref())
128                } else {
129                    None
130                }
131            }
132            Self::Rhs => {
133                if let Expr::Binary { rhs, .. } = &expr.extra {
134                    Some(rhs.as_ref())
135                } else {
136                    None
137                }
138            }
139
140            Self::TupleElement(Some(TupleIndex::Start(index))) => {
141                if let Expr::Tuple(elements) = &expr.extra {
142                    Some(&elements[*index])
143                } else {
144                    None
145                }
146            }
147
148            _ => None,
149        }
150    }
151
152    pub(super) fn walk_lvalue<'a>(
153        path: &[Self],
154        lvalue: &SpannedLvalue<'a, TypeAst<'a>>,
155    ) -> Spanned<'a> {
156        let lvalue = LvalueTree::from_lvalue(lvalue);
157        Self::walk(path, lvalue, Self::step_into_lvalue).with_no_extra()
158    }
159
160    pub(super) fn walk_destructure<'a>(
161        path: &[Self],
162        destructure: &Spanned<'a, Destructure<'a, TypeAst<'a>>>,
163    ) -> Spanned<'a> {
164        let destructure = LvalueTree::from_span(destructure);
165        Self::walk(path, destructure, Self::step_into_lvalue).with_no_extra()
166    }
167
168    fn step_into_lvalue<'r, 'a>(
169        &self,
170        lvalue: SpannedLvalueTree<'r, 'a>,
171    ) -> Option<SpannedLvalueTree<'r, 'a>> {
172        match lvalue.extra {
173            LvalueTree::Type(ty) => self.step_into_type(ty),
174            LvalueTree::Destructure(destructure) => self.step_into_destructure(destructure),
175            LvalueTree::JustSpan => None,
176        }
177    }
178
179    fn step_into_type<'r, 'a>(&self, ty: &'r TypeAst<'a>) -> Option<SpannedLvalueTree<'r, 'a>> {
180        match (self, ty) {
181            (Self::TupleElement(Some(i)), TypeAst::Tuple(tuple)) => {
182                i.get_from_tuple(tuple).map(LvalueTree::from_span)
183            }
184            (Self::TupleElement(Some(TupleIndex::Middle)), TypeAst::Slice(slice)) => {
185                Some(LvalueTree::from_span(&slice.element))
186            }
187            _ => None,
188        }
189    }
190
191    fn step_into_destructure<'r, 'a>(
192        &self,
193        destructure: &'r Destructure<'a, TypeAst<'a>>,
194    ) -> Option<SpannedLvalueTree<'r, 'a>> {
195        match self {
196            Self::TupleElement(Some(i)) => i.get_from_destructure(destructure),
197            _ => None,
198        }
199    }
200}
201
202/// Enumeration of all types encountered on the lvalue side of assignments.
203#[derive(Debug, Clone, Copy)]
204enum LvalueTree<'r, 'a> {
205    Destructure(&'r Destructure<'a, TypeAst<'a>>),
206    Type(&'r TypeAst<'a>),
207    JustSpan,
208}
209
210type SpannedLvalueTree<'r, 'a> = Spanned<'a, LvalueTree<'r, 'a>>;
211
212impl<'r, 'a> From<&'r Destructure<'a, TypeAst<'a>>> for LvalueTree<'r, 'a> {
213    fn from(destructure: &'r Destructure<'a, TypeAst<'a>>) -> Self {
214        Self::Destructure(destructure)
215    }
216}
217
218impl<'r, 'a> From<&'r TypeAst<'a>> for LvalueTree<'r, 'a> {
219    fn from(ty: &'r TypeAst<'a>) -> Self {
220        Self::Type(ty)
221    }
222}
223
224impl<'r> From<&'r ()> for LvalueTree<'r, '_> {
225    fn from((): &'r ()) -> Self {
226        Self::JustSpan
227    }
228}
229
230impl<'r, 'a> LvalueTree<'r, 'a> {
231    fn from_lvalue(lvalue: &'r Spanned<'a, Lvalue<'a, TypeAst<'a>>>) -> SpannedLvalueTree<'r, 'a> {
232        match &lvalue.extra {
233            Lvalue::Tuple(destructure) => lvalue.copy_with_extra(Self::Destructure(destructure)),
234            Lvalue::Variable { ty: Some(ty) } => ty.as_ref().map_extra(Self::Type),
235            _ => lvalue.copy_with_extra(Self::JustSpan),
236        }
237    }
238
239    fn from_span<T>(spanned: &'r Spanned<'a, T>) -> SpannedLvalueTree<'r, 'a>
240    where
241        &'r T: Into<Self>,
242    {
243        spanned.as_ref().map_extra(Into::into)
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use arithmetic_parser::{
250        grammars::{NumGrammar, Parse},
251        Statement,
252    };
253
254    use super::*;
255    use crate::Annotated;
256
257    type F32Grammar = Annotated<NumGrammar<f32>>;
258
259    fn parse_expr(code: &str) -> SpannedExpr<'_, F32Grammar> {
260        *F32Grammar::parse_statements(code)
261            .unwrap()
262            .return_value
263            .unwrap()
264    }
265
266    fn parse_lvalue(code: &str) -> SpannedLvalue<'_, TypeAst<'_>> {
267        let statement = F32Grammar::parse_statements(code)
268            .unwrap()
269            .statements
270            .pop()
271            .unwrap()
272            .extra;
273        match statement {
274            Statement::Assignment { lhs, .. } => lhs,
275            _ => panic!("Unexpected statement type: {statement:?}"),
276        }
277    }
278
279    #[test]
280    fn walking_simple_expr() {
281        let expr = parse_expr("1 + (2, x)");
282        let path = &[ErrorPathFragment::Rhs, TupleIndex::Start(1).into()];
283        let located = ErrorPathFragment::walk_expr(path, &expr);
284
285        assert_eq!(*located.fragment(), "x");
286    }
287
288    #[test]
289    fn walking_expr_with_fn_call() {
290        let expr = parse_expr("hash(1, (false, 2), x)");
291        let path = &[
292            ErrorPathFragment::FnArg(Some(TupleIndex::Start(1))),
293            TupleIndex::Start(0).into(),
294        ];
295        let located = ErrorPathFragment::walk_expr(path, &expr);
296
297        assert_eq!(*located.fragment(), "false");
298    }
299
300    #[test]
301    fn walking_expr_with_method_call() {
302        let expr = parse_expr("xs.map(|x| x + 1)");
303        let path = &[ErrorPathFragment::FnArg(Some(TupleIndex::Start(0)))];
304        let located = ErrorPathFragment::walk_expr(path, &expr);
305
306        assert_eq!(*located.fragment(), "xs");
307
308        let other_path = &[ErrorPathFragment::FnArg(Some(TupleIndex::Start(1)))];
309        let other_located = ErrorPathFragment::walk_expr(other_path, &expr);
310
311        assert_eq!(*other_located.fragment(), "|x| x + 1");
312    }
313
314    #[test]
315    fn walking_expr_with_partial_match() {
316        let expr = parse_expr("hash(1, xs)");
317        let path = &[
318            ErrorPathFragment::FnArg(Some(TupleIndex::Start(1))),
319            TupleIndex::Start(0).into(),
320        ];
321        let located = ErrorPathFragment::walk_expr(path, &expr);
322
323        assert_eq!(*located.fragment(), "xs");
324    }
325
326    #[test]
327    fn walking_expr_with_intermediate_type_cast() {
328        let expr = parse_expr("hash(1, (xs, ys) as Pair)");
329        let path = &[
330            ErrorPathFragment::FnArg(Some(TupleIndex::Start(1))),
331            TupleIndex::Start(0).into(),
332        ];
333        let located = ErrorPathFragment::walk_expr(path, &expr);
334
335        assert_eq!(*located.fragment(), "xs");
336    }
337
338    #[test]
339    fn walking_expr_with_final_type_cast() {
340        let expr = parse_expr("hash(1, (xs as [_] as Slice, ys))");
341        let path = &[
342            ErrorPathFragment::FnArg(Some(TupleIndex::Start(1))),
343            TupleIndex::Start(0).into(),
344        ];
345        let located = ErrorPathFragment::walk_expr(path, &expr);
346
347        assert_eq!(*located.fragment(), "xs");
348    }
349
350    #[test]
351    fn walking_lvalue() {
352        let lvalue = parse_lvalue("((u, v), ...ys, _, z) = x;");
353        let start_path = &[ErrorPathFragment::from(TupleIndex::Start(0))];
354        let located_start = ErrorPathFragment::walk_lvalue(start_path, &lvalue);
355        assert_eq!(*located_start.fragment(), "(u, v)");
356
357        let embedded_path = &[
358            ErrorPathFragment::from(TupleIndex::Start(0)),
359            ErrorPathFragment::from(TupleIndex::Start(1)),
360        ];
361        let embedded = ErrorPathFragment::walk_lvalue(embedded_path, &lvalue);
362        assert_eq!(*embedded.fragment(), "v");
363
364        let middle_path = &[ErrorPathFragment::from(TupleIndex::Middle)];
365        let located_middle = ErrorPathFragment::walk_lvalue(middle_path, &lvalue);
366        assert_eq!(*located_middle.fragment(), "ys");
367
368        let end_path = &[ErrorPathFragment::from(TupleIndex::End(1))];
369        let located_end = ErrorPathFragment::walk_lvalue(end_path, &lvalue);
370        assert_eq!(*located_end.fragment(), "z");
371    }
372
373    #[test]
374    fn walking_lvalue_with_annotations() {
375        let lvalue = parse_lvalue("x: (Bool, ...[(Num, Bool); _]) = x;");
376        let start_path = &[ErrorPathFragment::from(TupleIndex::Start(0))];
377        let located_start = ErrorPathFragment::walk_lvalue(start_path, &lvalue);
378        assert_eq!(*located_start.fragment(), "Bool");
379
380        let middle_path = &[ErrorPathFragment::from(TupleIndex::Middle)];
381        let located_middle = ErrorPathFragment::walk_lvalue(middle_path, &lvalue);
382        assert_eq!(*located_middle.fragment(), "(Num, Bool)");
383
384        let narrowed_path = &[
385            ErrorPathFragment::from(TupleIndex::Middle),
386            TupleIndex::Start(0).into(),
387        ];
388        let located_ty = ErrorPathFragment::walk_lvalue(narrowed_path, &lvalue);
389        assert_eq!(*located_ty.fragment(), "Num");
390    }
391
392    #[test]
393    fn walking_lvalue_with_annotation_mix() {
394        let lvalue = parse_lvalue("(flag, y: [Num]) = x;");
395        let start_path = &[ErrorPathFragment::from(TupleIndex::Start(0))];
396        let located_start = ErrorPathFragment::walk_lvalue(start_path, &lvalue);
397        assert_eq!(*located_start.fragment(), "flag");
398
399        let slice_path = &[ErrorPathFragment::from(TupleIndex::Start(1))];
400        let located_slice = ErrorPathFragment::walk_lvalue(slice_path, &lvalue);
401        assert_eq!(*located_slice.fragment(), "[Num]");
402
403        let slice_elem_path = &[
404            ErrorPathFragment::from(TupleIndex::Start(1)),
405            ErrorPathFragment::from(TupleIndex::Middle),
406        ];
407        let located_slice_elem = ErrorPathFragment::walk_lvalue(slice_elem_path, &lvalue);
408        assert_eq!(*located_slice_elem.fragment(), "Num");
409    }
410
411    #[test]
412    fn walking_slice() {
413        let lvalue = parse_lvalue("xs: [(Num, Bool); _] = x;");
414        let slice_path = &[ErrorPathFragment::from(TupleIndex::Middle)];
415        let located_slice = ErrorPathFragment::walk_lvalue(slice_path, &lvalue);
416        assert_eq!(*located_slice.fragment(), "(Num, Bool)");
417
418        let narrow_path = &[
419            ErrorPathFragment::from(TupleIndex::Middle),
420            ErrorPathFragment::from(TupleIndex::Start(1)),
421        ];
422        let located_elem = ErrorPathFragment::walk_lvalue(narrow_path, &lvalue);
423        assert_eq!(*located_elem.fragment(), "Bool");
424    }
425}