Skip to main content

cairo_lang_parser/
diagnostic.rs

1use cairo_lang_diagnostics::{DiagnosticEntry, error_code};
2use cairo_lang_filesystem::ids::{FileId, SpanInFile};
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    /// Converts a `SyntaxKind` to its corresponding operator string.
15    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    // TODO(spapini): Add tokens from the recovery set to the message.
99    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    MissingMacroRepetitionOperator,
131}
132
133impl<'a> DiagnosticEntry<'a> for ParserDiagnostic<'a> {
134    fn format(&self, _db: &'a dyn Database) -> String {
135        match &self.kind {
136            ParserDiagnosticKind::InvalidParamKindInMacroExpansion => {
137                "Parameter kinds are not allowed in macro expansion.".to_string()
138            }
139            ParserDiagnosticKind::InvalidParamKindInMacroRule => {
140                "Macro parameter must have a kind.".to_string()
141            }
142            ParserDiagnosticKind::SkippedElement { element_name } => {
143                format!("Skipped tokens. Expected: {element_name}.")
144            }
145            ParserDiagnosticKind::MissingToken(kind) => {
146                format!("Missing token {}.", self.kind_to_string(*kind))
147            }
148            ParserDiagnosticKind::MissingExpression => {
149                "Missing tokens. Expected an expression.".to_string()
150            }
151            ParserDiagnosticKind::MissingPathSegment => {
152                "Missing tokens. Expected a path segment.".to_string()
153            }
154            ParserDiagnosticKind::MissingTypeClause => {
155                "Unexpected token, expected ':' followed by a type.".to_string()
156            }
157            ParserDiagnosticKind::MissingTypeExpression => {
158                "Missing tokens. Expected a type expression.".to_string()
159            }
160            ParserDiagnosticKind::MissingWrappedArgList => "Missing tokens. Expected an argument \
161                                                            list wrapped in either parentheses, \
162                                                            brackets, or braces."
163                .to_string(),
164            ParserDiagnosticKind::MissingPattern => {
165                "Missing tokens. Expected a pattern.".to_string()
166            }
167            ParserDiagnosticKind::MissingMacroRuleParamKind => {
168                "Missing tokens. Expected a macro rule parameter kind.".to_string()
169            }
170            ParserDiagnosticKind::ExpectedInToken => {
171                "Missing identifier token, expected 'in'.".to_string()
172            }
173            ParserDiagnosticKind::ItemInlineMacroWithoutBang { identifier, bracket_type } => {
174                let (left, right) = match bracket_type {
175                    SyntaxKind::TerminalLParen => ("(", ")"),
176                    SyntaxKind::TerminalLBrack => ("[", "]"),
177                    SyntaxKind::TerminalLBrace => ("{", "}"),
178                    _ => ("", ""),
179                };
180                format!(
181                    "Expected a '!' after the identifier '{identifier}' to start an inline macro.
182Did you mean to write `{identifier}!{left}...{right}'?",
183                )
184            }
185            ParserDiagnosticKind::ReservedIdentifier { identifier } => {
186                format!("'{identifier}' is a reserved identifier.")
187            }
188            ParserDiagnosticKind::UnderscoreNotAllowedAsIdentifier => {
189                "An underscore ('_') is not allowed as an identifier in this context.".to_string()
190            }
191            ParserDiagnosticKind::MissingLiteralSuffix => "Missing literal suffix.".to_string(),
192            ParserDiagnosticKind::InvalidNumericLiteralValue => {
193                "Literal is not a valid number.".to_string()
194            }
195            ParserDiagnosticKind::IllegalStringEscaping => "Invalid string escaping.".to_string(),
196            ParserDiagnosticKind::ShortStringMustBeAscii => {
197                "Short string literals can only include ASCII characters.".into()
198            }
199            ParserDiagnosticKind::StringMustBeAscii => {
200                "String literals can only include ASCII characters.".into()
201            }
202            ParserDiagnosticKind::UnterminatedShortString => {
203                "Unterminated short string literal.".into()
204            }
205            ParserDiagnosticKind::UnterminatedString => "Unterminated string literal.".into(),
206            ParserDiagnosticKind::VisibilityWithoutItem => {
207                "Missing tokens. Expected an item after visibility.".to_string()
208            }
209            ParserDiagnosticKind::AttributesWithoutItem => {
210                "Missing tokens. Expected an item after attributes.".to_string()
211            }
212            ParserDiagnosticKind::AttributesWithoutTraitItem => {
213                "Missing tokens. Expected a trait item after attributes.".to_string()
214            }
215            ParserDiagnosticKind::AttributesWithoutImplItem => {
216                "Missing tokens. Expected an impl item after attributes.".to_string()
217            }
218            ParserDiagnosticKind::AttributesWithoutStatement => {
219                "Missing tokens. Expected a statement after attributes.".to_string()
220            }
221            ParserDiagnosticKind::DisallowedTrailingSeparatorOr => {
222                "A trailing `|` is not allowed in an or-pattern.".to_string()
223            }
224            ParserDiagnosticKind::ConsecutiveMathOperators { first_op, second_op } => {
225                format!(
226                    "Consecutive comparison operators are not allowed: {} followed by {}",
227                    self.kind_to_string(*first_op),
228                    self.kind_to_string(*second_op)
229                )
230            }
231            ParserDiagnosticKind::ExpectedSemicolonOrBody => {
232                "Expected either ';' or '{' after module name. Use ';' for an external module \
233                 declaration or '{' for a module with a body."
234                    .to_string()
235            }
236            ParserDiagnosticKind::LowPrecedenceOperatorInIfLet { op } => {
237                format!(
238                    "Operator {} is not allowed in let chains. Consider wrapping the expression \
239                     in parentheses.",
240                    self.kind_to_string(*op)
241                )
242            }
243            ParserDiagnosticKind::MissingMacroRepetitionOperator => {
244                "Missing macro repetition operator. Expected `?`, `+`, or `*` after `$(...)`."
245                    .to_string()
246            }
247        }
248    }
249
250    fn location(&self, _db: &'a dyn Database) -> SpanInFile<'a> {
251        SpanInFile { file_id: self.file_id, span: self.span }
252    }
253
254    fn error_code(&self) -> Option<cairo_lang_diagnostics::ErrorCode> {
255        Some(match &self.kind {
256            ParserDiagnosticKind::SkippedElement { .. } => error_code!(E1000),
257            ParserDiagnosticKind::MissingToken(_) => error_code!(E1001),
258            ParserDiagnosticKind::MissingExpression => error_code!(E1002),
259            ParserDiagnosticKind::MissingPathSegment => error_code!(E1003),
260            ParserDiagnosticKind::MissingTypeClause => error_code!(E1004),
261            ParserDiagnosticKind::MissingTypeExpression => error_code!(E1005),
262            ParserDiagnosticKind::MissingWrappedArgList => error_code!(E1006),
263            ParserDiagnosticKind::MissingPattern => error_code!(E1007),
264            ParserDiagnosticKind::MissingMacroRuleParamKind => error_code!(E1008),
265            ParserDiagnosticKind::InvalidParamKindInMacroExpansion => error_code!(E1009),
266            ParserDiagnosticKind::InvalidParamKindInMacroRule => error_code!(E1010),
267            ParserDiagnosticKind::ExpectedInToken => error_code!(E1011),
268            ParserDiagnosticKind::ItemInlineMacroWithoutBang { .. } => error_code!(E1012),
269            ParserDiagnosticKind::ReservedIdentifier { .. } => error_code!(E1013),
270            ParserDiagnosticKind::UnderscoreNotAllowedAsIdentifier => error_code!(E1014),
271            ParserDiagnosticKind::MissingLiteralSuffix => error_code!(E1015),
272            ParserDiagnosticKind::InvalidNumericLiteralValue => error_code!(E1016),
273            ParserDiagnosticKind::IllegalStringEscaping => error_code!(E1017),
274            ParserDiagnosticKind::ShortStringMustBeAscii => error_code!(E1018),
275            ParserDiagnosticKind::StringMustBeAscii => error_code!(E1019),
276            ParserDiagnosticKind::UnterminatedShortString => error_code!(E1020),
277            ParserDiagnosticKind::UnterminatedString => error_code!(E1021),
278            ParserDiagnosticKind::VisibilityWithoutItem => error_code!(E1022),
279            ParserDiagnosticKind::AttributesWithoutItem => error_code!(E1023),
280            ParserDiagnosticKind::AttributesWithoutTraitItem => error_code!(E1024),
281            ParserDiagnosticKind::AttributesWithoutImplItem => error_code!(E1025),
282            ParserDiagnosticKind::AttributesWithoutStatement => error_code!(E1026),
283            ParserDiagnosticKind::DisallowedTrailingSeparatorOr => error_code!(E1027),
284            ParserDiagnosticKind::ConsecutiveMathOperators { .. } => error_code!(E1028),
285            ParserDiagnosticKind::ExpectedSemicolonOrBody => error_code!(E1029),
286            ParserDiagnosticKind::LowPrecedenceOperatorInIfLet { .. } => error_code!(E1030),
287            ParserDiagnosticKind::MissingMacroRepetitionOperator => error_code!(E1031),
288        })
289    }
290
291    fn is_same_kind(&self, other: &Self) -> bool {
292        other.kind == self.kind
293    }
294}