1use cairo_lang_diagnostics::DiagnosticEntry;
2use cairo_lang_filesystem::ids::FileId;
3use cairo_lang_filesystem::span::TextSpan;
4use cairo_lang_syntax::node::kind::SyntaxKind;
5use salsa::Database;
6
7#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
8pub struct ParserDiagnostic<'a> {
9 pub file_id: FileId<'a>,
10 pub span: TextSpan,
11 pub kind: ParserDiagnosticKind,
12}
13impl<'a> ParserDiagnostic<'a> {
14 fn kind_to_string(&self, kind: SyntaxKind) -> String {
16 format!(
17 "'{}'",
18 match kind {
19 SyntaxKind::TerminalAnd => "&",
20 SyntaxKind::TerminalAndAnd => "&&",
21 SyntaxKind::TerminalArrow => "->",
22 SyntaxKind::TerminalAs => "as",
23 SyntaxKind::TerminalAt => "@",
24 SyntaxKind::TerminalBitNot => "~",
25 SyntaxKind::TerminalBreak => "break",
26 SyntaxKind::TerminalColon => ":",
27 SyntaxKind::TerminalColonColon => "::",
28 SyntaxKind::TerminalComma => ",",
29 SyntaxKind::TerminalConst => "const",
30 SyntaxKind::TerminalContinue => "continue",
31 SyntaxKind::TerminalDiv => "/",
32 SyntaxKind::TerminalDivEq => "/=",
33 SyntaxKind::TerminalDot => ".",
34 SyntaxKind::TerminalDotDot => "..",
35 SyntaxKind::TerminalDotDotEq => "..=",
36 SyntaxKind::TerminalElse => "else",
37 SyntaxKind::TerminalEnum => "enum",
38 SyntaxKind::TerminalEq => "=",
39 SyntaxKind::TerminalEqEq => "==",
40 SyntaxKind::TerminalExtern => "extern",
41 SyntaxKind::TerminalFalse => "false",
42 SyntaxKind::TerminalFor => "for",
43 SyntaxKind::TerminalFunction => "fn",
44 SyntaxKind::TerminalGE => ">=",
45 SyntaxKind::TerminalGT => ">",
46 SyntaxKind::TerminalHash => "#",
47 SyntaxKind::TerminalIf => "if",
48 SyntaxKind::TerminalImpl => "impl",
49 SyntaxKind::TerminalImplicits => "implicits",
50 SyntaxKind::TerminalLBrace => "{",
51 SyntaxKind::TerminalLBrack => "[",
52 SyntaxKind::TerminalLE => "<=",
53 SyntaxKind::TerminalLParen => "(",
54 SyntaxKind::TerminalLT => "<",
55 SyntaxKind::TerminalLet => "let",
56 SyntaxKind::TerminalLoop => "loop",
57 SyntaxKind::TerminalMatch => "match",
58 SyntaxKind::TerminalMatchArrow => "=>",
59 SyntaxKind::TerminalMinus => "-",
60 SyntaxKind::TerminalMinusEq => "-=",
61 SyntaxKind::TerminalMod => "%",
62 SyntaxKind::TerminalModEq => "%=",
63 SyntaxKind::TerminalModule => "mod",
64 SyntaxKind::TerminalMul => "*",
65 SyntaxKind::TerminalMulEq => "*=",
66 SyntaxKind::TerminalMut => "mut",
67 SyntaxKind::TerminalNeq => "!=",
68 SyntaxKind::TerminalNoPanic => "nopanic",
69 SyntaxKind::TerminalNot => "!",
70 SyntaxKind::TerminalOf => "of",
71 SyntaxKind::TerminalOr => "|",
72 SyntaxKind::TerminalOrOr => "||",
73 SyntaxKind::TerminalPlus => "+",
74 SyntaxKind::TerminalPlusEq => "+=",
75 SyntaxKind::TerminalPub => "pub",
76 SyntaxKind::TerminalQuestionMark => "?",
77 SyntaxKind::TerminalRBrace => "}",
78 SyntaxKind::TerminalRBrack => "]",
79 SyntaxKind::TerminalRParen => ")",
80 SyntaxKind::TerminalRef => "ref",
81 SyntaxKind::TerminalReturn => "return",
82 SyntaxKind::TerminalSemicolon => ";",
83 SyntaxKind::TerminalStruct => "struct",
84 SyntaxKind::TerminalTrait => "trait",
85 SyntaxKind::TerminalTrue => "true",
86 SyntaxKind::TerminalType => "type",
87 SyntaxKind::TerminalUnderscore => "_",
88 SyntaxKind::TerminalUse => "use",
89 SyntaxKind::TerminalWhile => "while",
90 SyntaxKind::TerminalXor => "^",
91 _ => return format!("{kind:?}"),
92 }
93 )
94 }
95}
96#[derive(Clone, Debug, Eq, Hash, PartialEq)]
97pub enum ParserDiagnosticKind {
98 SkippedElement { element_name: String },
100 MissingToken(SyntaxKind),
101 MissingExpression,
102 MissingPathSegment,
103 MissingTypeClause,
104 MissingTypeExpression,
105 MissingWrappedArgList,
106 MissingPattern,
107 MissingMacroRuleParamKind,
108 InvalidParamKindInMacroExpansion,
109 InvalidParamKindInMacroRule,
110 ExpectedInToken,
111 ItemInlineMacroWithoutBang { identifier: String, bracket_type: SyntaxKind },
112 ReservedIdentifier { identifier: String },
113 UnderscoreNotAllowedAsIdentifier,
114 MissingLiteralSuffix,
115 InvalidNumericLiteralValue,
116 IllegalStringEscaping,
117 ShortStringMustBeAscii,
118 StringMustBeAscii,
119 UnterminatedShortString,
120 UnterminatedString,
121 VisibilityWithoutItem,
122 AttributesWithoutItem,
123 AttributesWithoutTraitItem,
124 AttributesWithoutImplItem,
125 AttributesWithoutStatement,
126 DisallowedTrailingSeparatorOr,
127 ConsecutiveMathOperators { first_op: SyntaxKind, second_op: SyntaxKind },
128 ExpectedSemicolonOrBody,
129 LowPrecedenceOperatorInIfLet { op: SyntaxKind },
130}
131
132impl<'a> DiagnosticEntry<'a> for ParserDiagnostic<'a> {
133 fn format(&self, _db: &'a dyn Database) -> String {
134 match &self.kind {
135 ParserDiagnosticKind::InvalidParamKindInMacroExpansion => {
136 "Parameter kinds are not allowed in macro expansion.".to_string()
137 }
138 ParserDiagnosticKind::InvalidParamKindInMacroRule => {
139 "Macro parameter must have a kind.".to_string()
140 }
141 ParserDiagnosticKind::SkippedElement { element_name } => {
142 format!("Skipped tokens. Expected: {element_name}.")
143 }
144 ParserDiagnosticKind::MissingToken(kind) => {
145 format!("Missing token {}.", self.kind_to_string(*kind))
146 }
147 ParserDiagnosticKind::MissingExpression => {
148 "Missing tokens. Expected an expression.".to_string()
149 }
150 ParserDiagnosticKind::MissingPathSegment => {
151 "Missing tokens. Expected a path segment.".to_string()
152 }
153 ParserDiagnosticKind::MissingTypeClause => {
154 "Unexpected token, expected ':' followed by a type.".to_string()
155 }
156 ParserDiagnosticKind::MissingTypeExpression => {
157 "Missing tokens. Expected a type expression.".to_string()
158 }
159 ParserDiagnosticKind::MissingWrappedArgList => "Missing tokens. Expected an argument \
160 list wrapped in either parentheses, \
161 brackets, or braces."
162 .to_string(),
163 ParserDiagnosticKind::MissingPattern => {
164 "Missing tokens. Expected a pattern.".to_string()
165 }
166 ParserDiagnosticKind::MissingMacroRuleParamKind => {
167 "Missing tokens. Expected a macro rule parameter kind.".to_string()
168 }
169 ParserDiagnosticKind::ExpectedInToken => {
170 "Missing identifier token, expected 'in'.".to_string()
171 }
172 ParserDiagnosticKind::ItemInlineMacroWithoutBang { identifier, bracket_type } => {
173 let (left, right) = match bracket_type {
174 SyntaxKind::TerminalLParen => ("(", ")"),
175 SyntaxKind::TerminalLBrack => ("[", "]"),
176 SyntaxKind::TerminalLBrace => ("{", "}"),
177 _ => ("", ""),
178 };
179 format!(
180 "Expected a '!' after the identifier '{identifier}' to start an inline macro.
181Did you mean to write `{identifier}!{left}...{right}'?",
182 )
183 }
184 ParserDiagnosticKind::ReservedIdentifier { identifier } => {
185 format!("'{identifier}' is a reserved identifier.")
186 }
187 ParserDiagnosticKind::UnderscoreNotAllowedAsIdentifier => {
188 "An underscore ('_') is not allowed as an identifier in this context.".to_string()
189 }
190 ParserDiagnosticKind::MissingLiteralSuffix => "Missing literal suffix.".to_string(),
191 ParserDiagnosticKind::InvalidNumericLiteralValue => {
192 "Literal is not a valid number.".to_string()
193 }
194 ParserDiagnosticKind::IllegalStringEscaping => "Invalid string escaping.".to_string(),
195 ParserDiagnosticKind::ShortStringMustBeAscii => {
196 "Short string literals can only include ASCII characters.".into()
197 }
198 ParserDiagnosticKind::StringMustBeAscii => {
199 "String literals can only include ASCII characters.".into()
200 }
201 ParserDiagnosticKind::UnterminatedShortString => {
202 "Unterminated short string literal.".into()
203 }
204 ParserDiagnosticKind::UnterminatedString => "Unterminated string literal.".into(),
205 ParserDiagnosticKind::VisibilityWithoutItem => {
206 "Missing tokens. Expected an item after visibility.".to_string()
207 }
208 ParserDiagnosticKind::AttributesWithoutItem => {
209 "Missing tokens. Expected an item after attributes.".to_string()
210 }
211 ParserDiagnosticKind::AttributesWithoutTraitItem => {
212 "Missing tokens. Expected a trait item after attributes.".to_string()
213 }
214 ParserDiagnosticKind::AttributesWithoutImplItem => {
215 "Missing tokens. Expected an impl item after attributes.".to_string()
216 }
217 ParserDiagnosticKind::AttributesWithoutStatement => {
218 "Missing tokens. Expected a statement after attributes.".to_string()
219 }
220 ParserDiagnosticKind::DisallowedTrailingSeparatorOr => {
221 "A trailing `|` is not allowed in an or-pattern.".to_string()
222 }
223 ParserDiagnosticKind::ConsecutiveMathOperators { first_op, second_op } => {
224 format!(
225 "Consecutive comparison operators are not allowed: {} followed by {}",
226 self.kind_to_string(*first_op),
227 self.kind_to_string(*second_op)
228 )
229 }
230 ParserDiagnosticKind::ExpectedSemicolonOrBody => {
231 "Expected either ';' or '{' after module name. Use ';' for an external module \
232 declaration or '{' for a module with a body."
233 .to_string()
234 }
235 ParserDiagnosticKind::LowPrecedenceOperatorInIfLet { op } => {
236 format!(
237 "Operator {} is not allowed in let chains. Consider wrapping the expression \
238 in parentheses.",
239 self.kind_to_string(*op)
240 )
241 }
242 }
243 }
244
245 fn location(&self, _db: &'a dyn Database) -> cairo_lang_diagnostics::DiagnosticLocation<'a> {
246 cairo_lang_diagnostics::DiagnosticLocation { file_id: self.file_id, span: self.span }
247 }
248
249 fn is_same_kind(&self, other: &Self) -> bool {
250 other.kind == self.kind
251 }
252}