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