spade_parser/
error.rs

1use local_impl::local_impl;
2use spade_common::location_info::Loc;
3use spade_diagnostics::Diagnostic;
4use spade_macros::{IntoDiagnostic, IntoSubdiagnostic};
5
6use crate::{lexer::TokenKind, Token};
7
8pub type Result<T> = std::result::Result<T, Diagnostic>;
9
10#[derive(IntoSubdiagnostic)]
11#[diagnostic(suggestion, "Use `{` if you want to add items to this enum variant")]
12pub(crate) struct SuggestBraceEnumVariant {
13    #[diagnostic(replace, "{")]
14    pub open_paren: Loc<()>,
15    #[diagnostic(replace, "}")]
16    pub close_paren: Loc<()>,
17}
18
19#[derive(IntoDiagnostic, Clone)]
20#[diagnostic(error, "Expected argument list")]
21pub(crate) struct ExpectedArgumentList {
22    #[diagnostic(primary, "Expected argument list for this instantiation")]
23    pub base_expr: Loc<()>,
24    pub next_token: Token,
25}
26
27pub fn expected_pipeline_depth(got: &Token) -> Diagnostic {
28    let mut diag = Diagnostic::error(
29        got.loc(),
30        format!("expected pipeline depth, got `{}`", got.kind.as_str()),
31    )
32    .primary_label("expected pipeline depth here");
33    if got.kind != TokenKind::CloseParen {
34        diag.add_help("pipeline depth can only be integer constant");
35    }
36    diag
37}
38
39impl ExpectedArgumentList {
40    pub fn with_suggestions(self) -> Diagnostic {
41        let diag: Diagnostic = self.clone().into();
42        // If the next token is any kind of opening paren, we'll try to suggest changing that
43        if self.next_token.kind == TokenKind::OpenBrace
44            || self.next_token.kind == TokenKind::OpenBracket
45        {
46            diag.help("Positional argument lists start with`(`.")
47                .help("Named argument lists start with `$(`.")
48        } else {
49            // If not, we'll suggest inserting the argument list after the base expression. We
50            // *could* suggest it at the next token, but if the next token is on a new line,
51            // that gets confusing
52            diag.span_suggest_insert_after(
53                "Consider specifying positional arguments",
54                self.base_expr,
55                "(...)",
56            )
57            .span_suggest_insert_after("or named arguments", self.base_expr, "$(...)")
58        }
59    }
60}
61
62pub(crate) struct UnexpectedToken {
63    pub got: Token,
64    pub expected: Vec<&'static str>,
65}
66
67impl From<UnexpectedToken> for Diagnostic {
68    fn from(e: UnexpectedToken) -> Self {
69        // FIXME: derive(IntoDiagnostic) should be able to understand this
70        let expected_list = unexpected_token_list(e.expected);
71        let message = unexpected_token_message(&e.got.kind, &expected_list);
72
73        Diagnostic::error(e.got.loc(), message).primary_label(format!("expected {expected_list}"))
74    }
75}
76
77// Error returned by the comma_separated function. Must be explicitly converted
78// to the general Error using one of the member methods
79#[derive(Debug, Clone)]
80pub enum TokenSeparatedError {
81    Inner(Diagnostic),
82    UnexpectedToken {
83        got: Token,
84        separator: TokenKind,
85        end_tokens: Vec<TokenKind>,
86    },
87}
88
89pub type CommaSeparatedResult<T> = std::result::Result<T, TokenSeparatedError>;
90
91impl TokenSeparatedError {
92    pub fn extra_expected(self, mut extra: Vec<&'static str>) -> Diagnostic {
93        match self {
94            TokenSeparatedError::Inner(inner) => inner,
95            TokenSeparatedError::UnexpectedToken {
96                got,
97                separator,
98                end_tokens,
99            } => {
100                extra.push(separator.as_str());
101                for tok in end_tokens {
102                    extra.push(tok.as_str())
103                }
104                Diagnostic::from(UnexpectedToken {
105                    got,
106                    expected: extra,
107                })
108            }
109        }
110    }
111
112    pub fn no_context(self) -> Diagnostic {
113        self.extra_expected(vec![])
114    }
115}
116
117impl From<Diagnostic> for TokenSeparatedError {
118    fn from(value: Diagnostic) -> Self {
119        TokenSeparatedError::Inner(value)
120    }
121}
122
123#[local_impl]
124impl<T> CSErrorTransformations for std::result::Result<T, TokenSeparatedError> {
125    fn extra_expected(self, extra: Vec<&'static str>) -> Result<T> {
126        self.map_err(|e| e.extra_expected(extra))
127    }
128
129    fn no_context(self) -> Result<T> {
130        self.map_err(|e| e.no_context())
131    }
132}
133
134pub fn unexpected_token_list<'a>(expected: impl IntoIterator<Item = &'a str>) -> String {
135    let expected = expected
136        .into_iter()
137        .map(|s| format!("`{}`", s))
138        .collect::<Vec<_>>();
139
140    let count = expected.len();
141    if count == 1 {
142        expected[0].to_string()
143    } else if count == 2 {
144        format!("{} or {}", expected[0], expected[1])
145    } else {
146        format!(
147            "{}, or {}",
148            expected[0..(count - 1)].join(", "),
149            expected[count - 1]
150        )
151    }
152}
153
154pub fn unexpected_token_message(got: &TokenKind, expected_list: &str) -> String {
155    format!("Unexpected `{}`, expected {}", got.as_str(), expected_list,)
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn unexpected_token_works_for_single_token() {
164        let got = crate::lexer::TokenKind::Assignment;
165        let expected_list = unexpected_token_list(vec!["=="]);
166        let result = unexpected_token_message(&got, &expected_list);
167        assert_eq!(result, "Unexpected `=`, expected `==`");
168    }
169
170    #[test]
171    fn unexpected_token_works_for_multiple_tokens() {
172        let got = crate::lexer::TokenKind::Assignment;
173        let expected_list = unexpected_token_list(vec!["x", "y", "z"]);
174        let result = unexpected_token_message(&got, &expected_list);
175        assert_eq!(result, "Unexpected `=`, expected `x`, `y`, or `z`");
176    }
177}