arithmetic_typing/error/
location.rs

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