Skip to main content

spade_parser/
error.rs

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