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 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 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 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#[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}