cairo_lang_parser/
diagnostic.rs

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    /// 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}
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}