Skip to main content

ruff_python_parser/
error.rs

1use std::fmt::{self, Display};
2
3use ruff_python_ast::token::TokenKind;
4use ruff_python_ast::{ConstantValue, Expr, PythonVersion};
5use ruff_text_size::{Ranged, TextRange};
6
7use crate::string::InterpolatedStringKind;
8
9/// Represents represent errors that occur during parsing and are
10/// returned by the `parse_*` functions.
11#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize)]
12pub struct ParseError {
13    pub error: ParseErrorType,
14    pub location: TextRange,
15}
16
17impl std::ops::Deref for ParseError {
18    type Target = ParseErrorType;
19
20    fn deref(&self) -> &Self::Target {
21        &self.error
22    }
23}
24
25impl std::error::Error for ParseError {
26    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
27        Some(&self.error)
28    }
29}
30
31impl fmt::Display for ParseError {
32    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
33        write!(f, "{} at byte range {:?}", self.error, self.location)
34    }
35}
36
37impl From<LexicalError> for ParseError {
38    fn from(error: LexicalError) -> Self {
39        ParseError {
40            location: error.location(),
41            error: ParseErrorType::Lexical(error.into_error()),
42        }
43    }
44}
45
46impl Ranged for ParseError {
47    fn range(&self) -> TextRange {
48        self.location
49    }
50}
51
52impl ParseError {
53    pub fn error(self) -> ParseErrorType {
54        self.error
55    }
56}
57
58/// The reason an escape sequence in a string literal cannot be decoded.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, get_size2::GetSize)]
60pub enum UnicodeEscapeErrorKind {
61    /// `\x` is not followed by two hexadecimal digits.
62    TruncatedHexByte,
63    /// `\u` is not followed by four hexadecimal digits.
64    TruncatedShortUnicode,
65    /// `\U` is not followed by eight hexadecimal digits.
66    TruncatedLongUnicode,
67    /// `\U` names a code point above `U+10FFFF`.
68    IllegalCharacter,
69    /// `\N` is not followed by a non-empty name in braces.
70    MalformedName,
71    /// `\N{...}` names no Unicode character.
72    UnknownName,
73}
74
75impl Display for UnicodeEscapeErrorKind {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        f.write_str(match self {
78            Self::TruncatedHexByte => "truncated \\xXX escape",
79            Self::TruncatedShortUnicode => "truncated \\uXXXX escape",
80            Self::TruncatedLongUnicode => "truncated \\UXXXXXXXX escape",
81            Self::IllegalCharacter => "illegal Unicode character",
82            Self::MalformedName => "malformed \\N character escape",
83            Self::UnknownName => "unknown Unicode character name",
84        })
85    }
86}
87
88/// Represents the different types of errors that can occur during parsing of an f-string or t-string.
89#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
90pub enum InterpolatedStringErrorType {
91    /// Expected a right brace after an opened left brace.
92    UnclosedLbrace,
93    /// An invalid conversion flag was encountered.
94    InvalidConversionFlag,
95    /// A single right brace was encountered.
96    SingleRbrace,
97    /// Unterminated string, detected at the given one-based line.
98    UnterminatedString { detected_line: u32 },
99    /// Unterminated triple-quoted string, detected at the given one-based line.
100    UnterminatedTripleQuotedString { detected_line: u32 },
101    /// A lambda expression without parentheses was encountered.
102    LambdaWithoutParentheses,
103    /// Conversion flag does not immediately follow exclamation.
104    ConversionFlagNotImmediatelyAfterExclamation,
105    /// Newline inside of a format spec for a single quoted f- or t-string.
106    NewlineInFormatSpec,
107    /// A replacement field has no expression before the given separator.
108    ExpressionRequiredBefore(char),
109    /// A replacement field does not start with an expression.
110    ExpectedExpressionAfterLbrace,
111    /// The expression of a replacement field is not followed by `=`, `!`, `:` or `}`.
112    ExpectedSeparatorAfterExpression,
113    /// The `=` of a replacement field is not followed by `!`, `:` or `}`.
114    ExpectedSeparatorAfterDebug,
115    /// The conversion of a replacement field is not followed by `:` or `}`.
116    ExpectedSeparatorAfterConversion,
117    /// The format spec of a replacement field is not followed by `}`.
118    UnclosedFormatSpec,
119    /// A `!` is directly followed by `:` or `}`.
120    MissingConversionFlag,
121    /// A closing bracket closes the brace that opens a replacement field.
122    UnmatchedBracket(char),
123}
124
125impl std::fmt::Display for InterpolatedStringErrorType {
126    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
127        match self {
128            Self::UnclosedLbrace => f.write_str("expecting '}'"),
129            Self::InvalidConversionFlag => write!(f, "invalid conversion character"),
130            Self::SingleRbrace => f.write_str("single '}' is not allowed"),
131            Self::UnterminatedString { .. } => write!(f, "unterminated string"),
132            Self::UnterminatedTripleQuotedString { .. } => {
133                write!(f, "unterminated triple-quoted string")
134            }
135            Self::LambdaWithoutParentheses => {
136                write!(f, "lambda expressions are not allowed without parentheses")
137            }
138            Self::ConversionFlagNotImmediatelyAfterExclamation => write!(
139                f,
140                "conversion type must come right after the exclamation mark"
141            ),
142            Self::NewlineInFormatSpec => {
143                write!(
144                    f,
145                    "newlines are not allowed in format specifiers when using single quotes"
146                )
147            }
148            Self::ExpressionRequiredBefore(separator) => {
149                write!(f, "valid expression required before '{separator}'")
150            }
151            Self::ExpectedExpressionAfterLbrace => {
152                f.write_str("expecting a valid expression after '{'")
153            }
154            Self::ExpectedSeparatorAfterExpression => {
155                f.write_str("expecting '=', or '!', or ':', or '}'")
156            }
157            Self::ExpectedSeparatorAfterDebug => f.write_str("expecting '!', or ':', or '}'"),
158            Self::ExpectedSeparatorAfterConversion => f.write_str("expecting ':' or '}'"),
159            Self::UnclosedFormatSpec => f.write_str("expecting '}', or format specs"),
160            Self::MissingConversionFlag => f.write_str("missing conversion character"),
161            Self::UnmatchedBracket(closing) => write!(f, "unmatched '{closing}'"),
162        }
163    }
164}
165
166fn write_interpolated_string_error(
167    f: &mut std::fmt::Formatter,
168    kind: InterpolatedStringKind,
169    error: &InterpolatedStringErrorType,
170) -> std::fmt::Result {
171    match error {
172        InterpolatedStringErrorType::UnterminatedString { detected_line } => {
173            write!(
174                f,
175                "unterminated {kind} literal (detected at line {detected_line})"
176            )
177        }
178        InterpolatedStringErrorType::UnterminatedTripleQuotedString { detected_line } => {
179            write!(
180                f,
181                "unterminated triple-quoted {kind} literal (detected at line {detected_line})"
182            )
183        }
184        InterpolatedStringErrorType::NewlineInFormatSpec => write!(
185            f,
186            "{kind}: newlines are not allowed in format specifiers for single quoted {kind}s"
187        ),
188        _ => write!(f, "{kind}: {error}"),
189    }
190}
191
192/// Represents the different types of errors that can occur during parsing.
193#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize)]
194pub enum ParseErrorType {
195    /// An unexpected error occurred.
196    OtherError(String),
197
198    /// An error specific to stringified annotations occurred.
199    StringAnnotationError(&'static str),
200
201    /// An empty slice was found during parsing, e.g `data[]`.
202    EmptySlice,
203    /// An empty global names list was found during parsing.
204    EmptyGlobalNames,
205    /// An empty nonlocal names list was found during parsing.
206    EmptyNonlocalNames,
207    /// An empty delete targets list was found during parsing.
208    EmptyDeleteTargets,
209    /// An empty import names list was found during parsing.
210    EmptyImportNames,
211    /// An empty type parameter list was found during parsing.
212    EmptyTypeParams,
213
214    /// An unparenthesized named expression was found where it is not allowed.
215    UnparenthesizedNamedExpression,
216    /// An unparenthesized tuple expression was found where it is not allowed.
217    UnparenthesizedTupleExpression,
218    /// An unparenthesized generator expression was found where it is not allowed.
219    UnparenthesizedGeneratorExpression,
220
221    /// An invalid usage of a lambda expression was found.
222    InvalidLambdaExpressionUsage,
223    /// An invalid usage of a yield expression was found.
224    InvalidYieldExpressionUsage,
225    /// An invalid usage of a starred expression was found.
226    InvalidStarredExpressionUsage,
227    /// A star pattern was found outside a sequence pattern.
228    InvalidStarPatternUsage,
229    /// An underscore was used as a binding target in a match pattern.
230    InvalidMatchPatternTarget,
231
232    /// A parameter was found after a vararg.
233    ParamAfterVarKeywordParam,
234    /// A non-default parameter follows a default parameter.
235    NonDefaultParamAfterDefaultParam,
236    /// A default value was found for a `*` or `**` parameter.
237    VarParameterWithDefault,
238    /// A default value was found for a `**` parameter.
239    VarKeywordParameterWithDefault,
240    /// A dictionary key after the first item is not followed by a `:`. The error range is the
241    /// last character of the key.
242    ExpectedColonAfterDictionaryKey,
243
244    /// An invalid expression was found in the assignment target. `maybe_comparison` is set when
245    /// the `=` after it may have been meant as `==`.
246    InvalidAssignmentTarget {
247        kind: ExpressionKind,
248        maybe_comparison: bool,
249    },
250    /// A name is assigned to where `==` or `:=` may have been meant.
251    AssignmentInsteadOfComparison,
252    /// A `yield` expression is assigned to.
253    AssignmentToYield,
254    /// An invalid expression was found in the named assignment target.
255    InvalidNamedAssignmentTarget(ExpressionKind),
256    /// An invalid expression was found in the annotated assignment target.
257    InvalidAnnotatedAssignmentTarget,
258    /// An invalid expression was found in the augmented assignment target.
259    InvalidAugmentedAssignmentTarget(ExpressionKind),
260    /// An invalid expression was found in the delete target.
261    InvalidDeleteTarget(ExpressionKind),
262    /// An invalid expression was found after `as` in an import.
263    InvalidImportTarget(ExpressionKind),
264    /// An invalid expression was found after `as` in a pattern.
265    InvalidPatternTarget(ExpressionKind),
266    /// An invalid expression was found after `as` in an `except` or `except*` clause.
267    InvalidExceptTarget { kind: ExpressionKind, star: bool },
268
269    /// A positional argument was found after a keyword argument.
270    PositionalAfterKeywordArgument,
271    /// A positional argument was found after a keyword argument unpacking.
272    PositionalAfterKeywordUnpacking,
273    /// An iterable argument unpacking was found after keyword argument unpacking.
274    InvalidArgumentUnpackingOrder,
275    /// An invalid usage of iterable unpacking in a comprehension was found.
276    IterableUnpackingInComprehension,
277
278    /// Multiple simple statements were found in the same line without a `;` separating them.
279    SimpleStatementsOnSameLine,
280    /// A simple statement and a compound statement was found in the same line.
281    SimpleAndCompoundStatementOnSameLine,
282
283    /// Expected one or more keyword parameter after `*` separator.
284    ExpectedKeywordParam,
285    /// Expected a real number for a complex literal pattern.
286    ExpectedRealNumber,
287    /// Expected an imaginary number for a complex literal pattern.
288    ExpectedImaginaryNumber,
289    /// Expected an expression at the current parser location.
290    ExpectedExpression,
291    /// Expected an identifier at the current parser location.
292    ExpectedIdentifier,
293    /// The parser expected a specific token that was not found.
294    ExpectedToken {
295        expected: TokenKind,
296        found: TokenKind,
297    },
298
299    /// A compound statement header is not followed by an indented block. `line` is the line of
300    /// the header's keyword.
301    ExpectedIndentedBlock { clause: BlockClause, line: u32 },
302    /// An unexpected indentation was found during parsing.
303    UnexpectedIndentation,
304    /// The statement being parsed cannot be `async`.
305    UnexpectedTokenAfterAsync(TokenKind),
306    /// Ipython escape command was found
307    UnexpectedIpythonEscapeCommand,
308    /// An unexpected token was found at the end of an expression parsing
309    UnexpectedExpressionToken,
310
311    /// An f-string error containing the [`InterpolatedStringErrorType`].
312    FStringError(InterpolatedStringErrorType),
313    /// A t-string error containing the [`InterpolatedStringErrorType`].
314    TStringError(InterpolatedStringErrorType),
315    /// Parser encountered an error during lexing.
316    Lexical(LexicalErrorType),
317}
318
319/// The kind of an expression, as syntax errors name it.
320#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
321pub enum ExpressionKind {
322    Attribute,
323    Subscript,
324    Starred,
325    Name,
326    List,
327    Tuple,
328    Lambda,
329    FunctionCall,
330    Expression,
331    GeneratorExpression,
332    YieldExpression,
333    AwaitExpression,
334    ListComprehension,
335    SetComprehension,
336    DictComprehension,
337    DictLiteral,
338    SetDisplay,
339    FStringExpression,
340    TStringExpression,
341    None,
342    False,
343    True,
344    Ellipsis,
345    Literal,
346    Comparison,
347    ConditionalExpression,
348    NamedExpression,
349}
350
351impl ExpressionKind {
352    pub fn of(expr: &Expr) -> Self {
353        match expr {
354            Expr::Attribute(_) => Self::Attribute,
355            Expr::Subscript(_) => Self::Subscript,
356            Expr::Starred(_) => Self::Starred,
357            Expr::Name(_) => Self::Name,
358            Expr::List(_) => Self::List,
359            Expr::Tuple(_) => Self::Tuple,
360            Expr::Lambda(_) => Self::Lambda,
361            Expr::Call(_) => Self::FunctionCall,
362            Expr::BoolOp(_)
363            | Expr::BinOp(_)
364            | Expr::UnaryOp(_)
365            | Expr::Slice(_)
366            | Expr::IpyEscapeCommand(_) => Self::Expression,
367            Expr::Generator(_) => Self::GeneratorExpression,
368            Expr::Yield(_) | Expr::YieldFrom(_) => Self::YieldExpression,
369            Expr::Await(_) => Self::AwaitExpression,
370            Expr::ListComp(_) => Self::ListComprehension,
371            Expr::SetComp(_) => Self::SetComprehension,
372            Expr::DictComp(_) => Self::DictComprehension,
373            Expr::Dict(_) => Self::DictLiteral,
374            Expr::Set(_) => Self::SetDisplay,
375            Expr::FString(_) => Self::FStringExpression,
376            Expr::TString(_) => Self::TStringExpression,
377            Expr::NoneLiteral(_) => Self::None,
378            Expr::BooleanLiteral(literal) if literal.value => Self::True,
379            Expr::BooleanLiteral(_) => Self::False,
380            Expr::EllipsisLiteral(_) => Self::Ellipsis,
381            Expr::StringLiteral(_) | Expr::BytesLiteral(_) | Expr::NumberLiteral(_) => {
382                Self::Literal
383            }
384            Expr::Compare(_) => Self::Comparison,
385            Expr::If(_) => Self::ConditionalExpression,
386            Expr::Named(_) => Self::NamedExpression,
387            Expr::Constant(constant) => match constant.value {
388                ConstantValue::None => Self::None,
389                ConstantValue::Boolean(true) => Self::True,
390                ConstantValue::Boolean(false) => Self::False,
391                ConstantValue::Ellipsis => Self::Ellipsis,
392                _ => Self::Literal,
393            },
394        }
395    }
396}
397
398impl std::fmt::Display for ExpressionKind {
399    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
400        f.write_str(match self {
401            Self::Attribute => "attribute",
402            Self::Subscript => "subscript",
403            Self::Starred => "starred",
404            Self::Name => "name",
405            Self::List => "list",
406            Self::Tuple => "tuple",
407            Self::Lambda => "lambda",
408            Self::FunctionCall => "function call",
409            Self::Expression => "expression",
410            Self::GeneratorExpression => "generator expression",
411            Self::YieldExpression => "yield expression",
412            Self::AwaitExpression => "await expression",
413            Self::ListComprehension => "list comprehension",
414            Self::SetComprehension => "set comprehension",
415            Self::DictComprehension => "dict comprehension",
416            Self::DictLiteral => "dict literal",
417            Self::SetDisplay => "set display",
418            Self::FStringExpression => "f-string expression",
419            Self::TStringExpression => "t-string expression",
420            Self::None => "None",
421            Self::False => "False",
422            Self::True => "True",
423            Self::Ellipsis => "ellipsis",
424            Self::Literal => "literal",
425            Self::Comparison => "comparison",
426            Self::ConditionalExpression => "conditional expression",
427            Self::NamedExpression => "named expression",
428        })
429    }
430}
431
432/// The compound statement clause whose header precedes an indented block.
433#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
434pub enum BlockClause {
435    If,
436    Elif,
437    Else,
438    For,
439    With,
440    While,
441    Try,
442    Except,
443    ExceptStar,
444    Finally,
445    Match,
446    Case,
447    Class,
448    FunctionDef,
449}
450
451impl std::fmt::Display for BlockClause {
452    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
453        f.write_str(match self {
454            Self::If => "'if' statement",
455            Self::Elif => "'elif' statement",
456            Self::Else => "'else' statement",
457            Self::For => "'for' statement",
458            Self::With => "'with' statement",
459            Self::While => "'while' statement",
460            Self::Try => "'try' statement",
461            Self::Except => "'except' statement",
462            Self::ExceptStar => "'except*' statement",
463            Self::Finally => "'finally' statement",
464            Self::Match => "'match' statement",
465            Self::Case => "'case' statement",
466            Self::Class => "class definition",
467            Self::FunctionDef => "function definition",
468        })
469    }
470}
471
472impl ParseErrorType {
473    pub(crate) fn from_interpolated_string_error(
474        error: InterpolatedStringErrorType,
475        string_kind: InterpolatedStringKind,
476    ) -> Self {
477        match string_kind {
478            InterpolatedStringKind::FString => Self::FStringError(error),
479            InterpolatedStringKind::TString => Self::TStringError(error),
480        }
481    }
482}
483
484impl std::error::Error for ParseErrorType {}
485
486impl std::fmt::Display for ParseErrorType {
487    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
488        match self {
489            ParseErrorType::OtherError(msg) => f.write_str(msg),
490            ParseErrorType::ExpectedIndentedBlock { clause, line } => {
491                write!(
492                    f,
493                    "expected an indented block after {clause} on line {line}"
494                )
495            }
496            ParseErrorType::StringAnnotationError(msg) => f.write_str(msg),
497            ParseErrorType::ExpectedToken { found, expected } => match (*expected, *found) {
498                (TokenKind::Colon, TokenKind::Newline) => f.write_str("expected ':'"),
499                (TokenKind::Lpar, _) => f.write_str("expected '('"),
500                (TokenKind::Else, found) if found != TokenKind::Colon => {
501                    f.write_str("expected 'else' after 'if' expression")
502                }
503                _ => f.write_str("invalid syntax"),
504            },
505            ParseErrorType::Lexical(lex_error) => write!(f, "{lex_error}"),
506            ParseErrorType::SimpleStatementsOnSameLine => f.write_str("invalid syntax"),
507            ParseErrorType::SimpleAndCompoundStatementOnSameLine => f.write_str("invalid syntax"),
508            ParseErrorType::UnexpectedTokenAfterAsync(kind) => {
509                write!(
510                    f,
511                    "expected `def`, `with` or `for` to follow `async`, found {kind}",
512                )
513            }
514            ParseErrorType::InvalidArgumentUnpackingOrder => {
515                f.write_str("iterable argument unpacking follows keyword argument unpacking")
516            }
517            ParseErrorType::IterableUnpackingInComprehension => {
518                f.write_str("iterable unpacking cannot be used in a comprehension")
519            }
520            ParseErrorType::UnparenthesizedNamedExpression => {
521                f.write_str("unparenthesized named expression cannot be used here")
522            }
523            ParseErrorType::UnparenthesizedTupleExpression => {
524                f.write_str("unparenthesized tuple expression cannot be used here")
525            }
526            ParseErrorType::UnparenthesizedGeneratorExpression => {
527                f.write_str("Generator expression must be parenthesized")
528            }
529            ParseErrorType::InvalidYieldExpressionUsage => {
530                f.write_str("yield expression cannot be used here")
531            }
532            ParseErrorType::InvalidLambdaExpressionUsage => {
533                f.write_str("lambda expression cannot be used here")
534            }
535            ParseErrorType::InvalidStarredExpressionUsage => f.write_str("invalid syntax"),
536            ParseErrorType::PositionalAfterKeywordArgument => {
537                f.write_str("positional argument follows keyword argument")
538            }
539            ParseErrorType::PositionalAfterKeywordUnpacking => {
540                f.write_str("positional argument follows keyword argument unpacking")
541            }
542            ParseErrorType::EmptySlice => f.write_str("expected index or slice expression"),
543            ParseErrorType::EmptyGlobalNames => {
544                f.write_str("global statement must have at least one name")
545            }
546            ParseErrorType::EmptyNonlocalNames => {
547                f.write_str("nonlocal statement must have at least one name")
548            }
549            ParseErrorType::EmptyDeleteTargets => {
550                f.write_str("delete statement must have at least one target")
551            }
552            ParseErrorType::EmptyImportNames => {
553                f.write_str("Expected one or more names after 'import'")
554            }
555            ParseErrorType::EmptyTypeParams => f.write_str("Type parameter list cannot be empty"),
556            ParseErrorType::ParamAfterVarKeywordParam => {
557                f.write_str("arguments cannot follow var-keyword argument")
558            }
559            ParseErrorType::NonDefaultParamAfterDefaultParam => {
560                f.write_str("parameter without a default follows parameter with a default")
561            }
562            ParseErrorType::ExpectedKeywordParam => {
563                f.write_str("named arguments must follow bare *")
564            }
565            ParseErrorType::VarKeywordParameterWithDefault => {
566                f.write_str("var-keyword argument cannot have default value")
567            }
568            ParseErrorType::ExpectedColonAfterDictionaryKey => {
569                f.write_str("':' expected after dictionary key")
570            }
571            ParseErrorType::VarParameterWithDefault => {
572                f.write_str("var-positional argument cannot have default value")
573            }
574            ParseErrorType::InvalidStarPatternUsage => {
575                f.write_str("cannot use starred expression here")
576            }
577            ParseErrorType::InvalidMatchPatternTarget => f.write_str("cannot use '_' as a target"),
578            ParseErrorType::ExpectedRealNumber => {
579                f.write_str("expected a real number in complex literal pattern")
580            }
581            ParseErrorType::ExpectedImaginaryNumber => {
582                f.write_str("expected an imaginary number in complex literal pattern")
583            }
584            ParseErrorType::ExpectedExpression | ParseErrorType::ExpectedIdentifier => {
585                f.write_str("invalid syntax")
586            }
587            ParseErrorType::UnexpectedIndentation => f.write_str("unexpected indent"),
588            ParseErrorType::InvalidAssignmentTarget {
589                kind,
590                maybe_comparison: false,
591            } => write!(f, "cannot assign to {kind}"),
592            ParseErrorType::InvalidAssignmentTarget {
593                kind,
594                maybe_comparison: true,
595            } => write!(
596                f,
597                "cannot assign to {kind} here. Maybe you meant '==' instead of '='?"
598            ),
599            ParseErrorType::AssignmentInsteadOfComparison => {
600                f.write_str("invalid syntax. Maybe you meant '==' or ':=' instead of '='?")
601            }
602            ParseErrorType::AssignmentToYield => {
603                f.write_str("assignment to yield expression not possible")
604            }
605            ParseErrorType::InvalidAnnotatedAssignmentTarget => {
606                f.write_str("illegal target for annotation")
607            }
608            ParseErrorType::InvalidNamedAssignmentTarget(kind) => {
609                write!(f, "cannot use assignment expressions with {kind}")
610            }
611            ParseErrorType::InvalidAugmentedAssignmentTarget(kind) => {
612                write!(
613                    f,
614                    "'{kind}' is an illegal expression for augmented assignment"
615                )
616            }
617            ParseErrorType::InvalidDeleteTarget(kind) => write!(f, "cannot delete {kind}"),
618            ParseErrorType::InvalidImportTarget(kind) => {
619                write!(f, "cannot use {kind} as import target")
620            }
621            ParseErrorType::InvalidPatternTarget(kind) => {
622                write!(f, "cannot use {kind} as pattern target")
623            }
624            ParseErrorType::InvalidExceptTarget { kind, star } => {
625                let clause = if *star { "except*" } else { "except" };
626                write!(f, "cannot use {clause} statement with {kind}")
627            }
628            ParseErrorType::UnexpectedIpythonEscapeCommand => {
629                f.write_str("IPython escape commands are only allowed in `Mode::Ipython`")
630            }
631            ParseErrorType::FStringError(error) => {
632                write_interpolated_string_error(f, InterpolatedStringKind::FString, error)
633            }
634            ParseErrorType::TStringError(error) => {
635                write_interpolated_string_error(f, InterpolatedStringKind::TString, error)
636            }
637            ParseErrorType::UnexpectedExpressionToken => f.write_str("invalid syntax"),
638        }
639    }
640}
641
642/// Represents an error that occur during lexing and are
643/// returned by the `parse_*` functions in the iterator in the
644/// [lexer] implementation.
645///
646/// [lexer]: crate::lexer
647#[derive(Debug, Clone, PartialEq)]
648pub struct LexicalError {
649    /// The type of error that occurred.
650    error: LexicalErrorType,
651    /// The location of the error.
652    location: TextRange,
653}
654
655impl LexicalError {
656    /// Creates a new `LexicalError` with the given error type and location.
657    pub fn new(error: LexicalErrorType, location: TextRange) -> Self {
658        Self { error, location }
659    }
660
661    pub fn error(&self) -> &LexicalErrorType {
662        &self.error
663    }
664
665    pub fn into_error(self) -> LexicalErrorType {
666        self.error
667    }
668
669    pub fn location(&self) -> TextRange {
670        self.location
671    }
672}
673
674impl std::ops::Deref for LexicalError {
675    type Target = LexicalErrorType;
676
677    fn deref(&self) -> &Self::Target {
678        self.error()
679    }
680}
681
682impl std::error::Error for LexicalError {
683    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
684        Some(self.error())
685    }
686}
687
688impl std::fmt::Display for LexicalError {
689    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
690        write!(
691            f,
692            "{} at byte offset {}",
693            self.error(),
694            u32::from(self.location().start())
695        )
696    }
697}
698
699/// Represents the different types of errors that can occur during lexing.
700#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
701pub enum LexicalErrorType {
702    // TODO: Can probably be removed, the places it is used seem to be able
703    // to use the `UnicodeError` variant instead.
704    #[doc(hidden)]
705    StringError,
706    /// A string literal without the closing quote, detected at the given one-based line.
707    UnclosedStringError {
708        triple_quoted: bool,
709        /// The literal contains a backslash followed by its quote character.
710        escaped_end_quote: bool,
711        detected_line: u32,
712    },
713    /// An escape sequence in a string literal cannot be decoded. `start` and `end` are the
714    /// inclusive positions of the escape sequence in the string content, where a non-ASCII
715    /// character counts as its ten-character `\U` escape.
716    UnicodeEscapeError {
717        kind: UnicodeEscapeErrorKind,
718        start: u32,
719        end: u32,
720    },
721    /// A `\x` escape in a bytes literal at the given position of its content is not followed by
722    /// two hexadecimal digits.
723    BytesEscapeError { position: u32 },
724    /// A dedent does not match any outer indentation level.
725    IndentationError,
726    /// Tabs and spaces are mixed in a way that makes the indentation depend on the tab size.
727    TabError,
728    /// The indentation is nested too deeply.
729    TooDeepIndentation,
730    /// An unrecognized token was encountered.
731    UnrecognizedToken { tok: char },
732    /// An f-string error containing the [`InterpolatedStringErrorType`].
733    FStringError(InterpolatedStringErrorType),
734    /// A t-string error containing the [`InterpolatedStringErrorType`].
735    TStringError(InterpolatedStringErrorType),
736    /// Invalid character encountered in a byte literal.
737    InvalidByteLiteral,
738    /// An unexpected character was encountered after a line continuation.
739    LineContinuationError,
740    /// An unexpected end of file was encountered.
741    Eof,
742    /// A closing bracket without an opening bracket.
743    UnmatchedBracket { closing: char },
744    /// A closing bracket that does not match the innermost opening bracket. `opening_line` is the
745    /// line of the opening bracket if it is on another line.
746    MismatchedBracket {
747        closing: char,
748        opening: char,
749        opening_line: Option<u32>,
750    },
751    /// An opening bracket that is not closed by the end of the source.
752    ///
753    /// `incomplete` is whether more input could still close it: the parser read to the end of the
754    /// source with the bracket open. Otherwise, the error is reported in place of an earlier syntax
755    /// error.
756    UnclosedBracket { opening: char, incomplete: bool },
757    /// Brackets are nested too deeply.
758    TooDeeplyNestedBrackets,
759    /// A malformed number literal, or one directly followed by a name.
760    InvalidNumberLiteral { kind: NumberLiteralKind },
761    /// A digit that is not valid in the octal or binary literal it appears in.
762    InvalidDigit {
763        digit: char,
764        kind: NumberLiteralKind,
765    },
766    /// A decimal integer literal with leading zeros.
767    LeadingZerosInDecimalInteger,
768    /// A string prefix that combines incompatible prefixes, such as `ub`.
769    IncompatibleStringPrefixes { first: char, second: char },
770    /// An unexpected error occurred.
771    OtherError(Box<str>),
772}
773
774impl std::error::Error for LexicalErrorType {}
775
776/// The kind of a number literal, as named in error messages.
777#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
778pub enum NumberLiteralKind {
779    Decimal,
780    Hexadecimal,
781    Octal,
782    Binary,
783    Imaginary,
784}
785
786impl std::fmt::Display for NumberLiteralKind {
787    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
788        f.write_str(match self {
789            Self::Decimal => "decimal",
790            Self::Hexadecimal => "hexadecimal",
791            Self::Octal => "octal",
792            Self::Binary => "binary",
793            Self::Imaginary => "imaginary",
794        })
795    }
796}
797
798/// Whether `c` is printable: not a control, format, surrogate, private use, unassigned or
799/// separator character, other than the ASCII space.
800fn is_printable(c: char) -> bool {
801    use icu_properties::props::{EnumeratedProperty, GeneralCategory};
802
803    c == ' '
804        || !matches!(
805            GeneralCategory::for_char(c),
806            GeneralCategory::SpaceSeparator
807                | GeneralCategory::LineSeparator
808                | GeneralCategory::ParagraphSeparator
809                | GeneralCategory::Control
810                | GeneralCategory::Format
811                | GeneralCategory::Surrogate
812                | GeneralCategory::PrivateUse
813                | GeneralCategory::Unassigned
814        )
815}
816
817impl LexicalErrorType {
818    /// Returns `true` if the error reports a string literal without its closing quotes.
819    pub(crate) fn is_unclosed_string_error(&self) -> bool {
820        matches!(
821            self,
822            Self::UnclosedStringError { .. }
823                | Self::FStringError(InterpolatedStringErrorType::UnclosedLbrace)
824                | Self::TStringError(InterpolatedStringErrorType::UnclosedLbrace)
825        )
826    }
827
828    /// Returns `true` if the error stops tokenization, as opposed to an error found while
829    /// decoding the content of a token.
830    pub fn is_tokenizer_error(&self) -> bool {
831        match self {
832            Self::StringError
833            | Self::UnicodeEscapeError { .. }
834            | Self::BytesEscapeError { .. }
835            | Self::InvalidByteLiteral
836            | Self::OtherError(_) => false,
837            // An ASCII punctuation character is a token that the parser rejects.
838            Self::UnrecognizedToken { tok } => !tok.is_ascii() || !is_printable(*tok),
839            Self::FStringError(error) | Self::TStringError(error) => matches!(
840                error,
841                InterpolatedStringErrorType::UnterminatedString { .. }
842                    | InterpolatedStringErrorType::UnterminatedTripleQuotedString { .. }
843                    | InterpolatedStringErrorType::SingleRbrace
844                    | InterpolatedStringErrorType::NewlineInFormatSpec
845                    | InterpolatedStringErrorType::UnclosedLbrace
846                    | InterpolatedStringErrorType::UnmatchedBracket(_)
847            ),
848            Self::UnclosedStringError { .. }
849            | Self::IndentationError
850            | Self::TabError
851            | Self::TooDeepIndentation
852            | Self::LineContinuationError
853            | Self::Eof
854            | Self::UnmatchedBracket { .. }
855            | Self::MismatchedBracket { .. }
856            | Self::UnclosedBracket { .. }
857            | Self::TooDeeplyNestedBrackets
858            | Self::InvalidNumberLiteral { .. }
859            | Self::InvalidDigit { .. }
860            | Self::LeadingZerosInDecimalInteger
861            | Self::IncompatibleStringPrefixes { .. } => true,
862        }
863    }
864
865    pub(crate) fn from_interpolated_string_error(
866        error: InterpolatedStringErrorType,
867        string_kind: InterpolatedStringKind,
868    ) -> Self {
869        match string_kind {
870            InterpolatedStringKind::FString => Self::FStringError(error),
871            InterpolatedStringKind::TString => Self::TStringError(error),
872        }
873    }
874}
875
876impl std::fmt::Display for LexicalErrorType {
877    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
878        match self {
879            Self::StringError => write!(f, "got unexpected string"),
880            Self::FStringError(error) => {
881                write_interpolated_string_error(f, InterpolatedStringKind::FString, error)
882            }
883            Self::TStringError(error) => {
884                write_interpolated_string_error(f, InterpolatedStringKind::TString, error)
885            }
886            Self::InvalidByteLiteral => {
887                write!(f, "bytes can only contain ASCII literal characters")
888            }
889            Self::UnicodeEscapeError { kind, start, end } => write!(
890                f,
891                "(unicode error) 'unicodeescape' codec can't decode bytes in position {start}-{end}: {kind}"
892            ),
893            Self::BytesEscapeError { position } => {
894                write!(f, "(value error) invalid \\x escape at position {position}")
895            }
896            Self::IndentationError => {
897                write!(f, "unindent does not match any outer indentation level")
898            }
899            Self::TabError => write!(f, "inconsistent use of tabs and spaces in indentation"),
900            Self::TooDeepIndentation => write!(f, "too many levels of indentation"),
901            Self::UnrecognizedToken { tok } if !is_printable(*tok) => {
902                write!(
903                    f,
904                    "invalid non-printable character U+{:04X}",
905                    u32::from(*tok)
906                )
907            }
908            Self::UnrecognizedToken { tok } if !tok.is_ascii() => {
909                write!(f, "invalid character '{tok}' (U+{:04X})", u32::from(*tok))
910            }
911            Self::UnrecognizedToken { .. } => f.write_str("invalid syntax"),
912            Self::InvalidNumberLiteral { kind } => write!(f, "invalid {kind} literal"),
913            Self::InvalidDigit { digit, kind } => {
914                write!(f, "invalid digit '{digit}' in {kind} literal")
915            }
916            Self::LeadingZerosInDecimalInteger => f.write_str(
917                "leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers",
918            ),
919            Self::IncompatibleStringPrefixes { first, second } => {
920                write!(f, "'{first}' and '{second}' prefixes are incompatible")
921            }
922            Self::LineContinuationError => {
923                write!(f, "unexpected character after line continuation character")
924            }
925            Self::Eof => write!(f, "unexpected EOF while parsing"),
926            Self::UnmatchedBracket { closing } => write!(f, "unmatched '{closing}'"),
927            Self::MismatchedBracket {
928                closing,
929                opening,
930                opening_line,
931            } => {
932                write!(
933                    f,
934                    "closing parenthesis '{closing}' does not match opening parenthesis '{opening}'"
935                )?;
936                if let Some(line) = opening_line {
937                    write!(f, " on line {line}")?;
938                }
939                Ok(())
940            }
941            Self::UnclosedBracket { opening, .. } => write!(f, "'{opening}' was never closed"),
942            Self::TooDeeplyNestedBrackets => f.write_str("too many nested parentheses"),
943            Self::OtherError(msg) => write!(f, "{msg}"),
944            Self::UnclosedStringError {
945                triple_quoted: true,
946                detected_line,
947                ..
948            } => write!(
949                f,
950                "unterminated triple-quoted string literal (detected at line {detected_line})"
951            ),
952            Self::UnclosedStringError {
953                escaped_end_quote,
954                detected_line,
955                ..
956            } => {
957                write!(
958                    f,
959                    "unterminated string literal (detected at line {detected_line})"
960                )?;
961                if *escaped_end_quote {
962                    f.write_str("; perhaps you escaped the end quote?")?;
963                }
964                Ok(())
965            }
966        }
967    }
968}
969
970/// Represents a version-related syntax error detected during parsing.
971///
972/// An example of a version-related error is the use of a `match` statement before Python 3.10, when
973/// it was first introduced. See [`UnsupportedSyntaxErrorKind`] for other kinds of errors.
974#[derive(Debug, PartialEq, Clone, get_size2::GetSize)]
975pub struct UnsupportedSyntaxError {
976    pub kind: UnsupportedSyntaxErrorKind,
977    pub range: TextRange,
978    /// The target [`PythonVersion`] for which this error was detected.
979    pub target_version: PythonVersion,
980}
981
982impl Ranged for UnsupportedSyntaxError {
983    fn range(&self) -> TextRange {
984        self.range
985    }
986}
987
988/// The type of tuple unpacking for [`UnsupportedSyntaxErrorKind::StarTuple`].
989#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, get_size2::GetSize)]
990pub enum StarTupleKind {
991    Return,
992    Yield,
993}
994
995/// The type of PEP 701 f-string error for [`UnsupportedSyntaxErrorKind::Pep701FString`].
996#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, get_size2::GetSize)]
997pub enum FStringKind {
998    Backslash,
999    Comment,
1000    LineBreak,
1001    NestedQuote,
1002}
1003
1004/// The type of PEP 798 unpacking-comprehension error for
1005/// [`UnsupportedSyntaxErrorKind::UnpackingInComprehension`].
1006#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, get_size2::GetSize)]
1007pub enum ComprehensionUnpackingKind {
1008    IterableInList,
1009    IterableInSet,
1010    IterableInGenerator,
1011    DictInDict,
1012}
1013
1014#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, get_size2::GetSize)]
1015pub enum UnparenthesizedNamedExprKind {
1016    SequenceIndex,
1017    SetLiteral,
1018    SetComprehension,
1019}
1020
1021#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, get_size2::GetSize)]
1022pub enum UnsupportedSyntaxErrorKind {
1023    Match,
1024    Walrus,
1025    ExceptStar,
1026    /// Represents the use of an unparenthesized named expression (`:=`) in a set literal, set
1027    /// comprehension, or sequence index before Python 3.10.
1028    ///
1029    /// ## Examples
1030    ///
1031    /// These are allowed on Python 3.10:
1032    ///
1033    /// ```python
1034    /// {x := 1, 2, 3}                 # set literal
1035    /// {last := x for x in range(3)}  # set comprehension
1036    /// lst[x := 1]                    # sequence index
1037    /// ```
1038    ///
1039    /// But on Python 3.9 the named expression needs to be parenthesized:
1040    ///
1041    /// ```python
1042    /// {(x := 1), 2, 3}                 # set literal
1043    /// {(last := x) for x in range(3)}  # set comprehension
1044    /// lst[(x := 1)]                    # sequence index
1045    /// ```
1046    ///
1047    /// However, unparenthesized named expressions are never allowed in slices:
1048    ///
1049    /// ```python
1050    /// lst[x:=1:-1]      # syntax error
1051    /// lst[1:x:=1]       # syntax error
1052    /// lst[1:3:x:=1]     # syntax error
1053    ///
1054    /// lst[(x:=1):-1]    # ok
1055    /// lst[1:(x:=1)]     # ok
1056    /// lst[1:3:(x:=1)]   # ok
1057    /// ```
1058    ///
1059    /// ## References
1060    ///
1061    /// - [Python 3.10 Other Language Changes](https://docs.python.org/3/whatsnew/3.10.html#other-language-changes)
1062    UnparenthesizedNamedExpr(UnparenthesizedNamedExprKind),
1063
1064    /// Represents the use of a parenthesized keyword argument name after Python 3.8.
1065    ///
1066    /// ## Example
1067    ///
1068    /// From [BPO 34641] it sounds like this was only accidentally supported and was removed when
1069    /// noticed. Code like this used to be valid:
1070    ///
1071    /// ```python
1072    /// f((a)=1)
1073    /// ```
1074    ///
1075    /// After Python 3.8, you have to omit the parentheses around `a`:
1076    ///
1077    /// ```python
1078    /// f(a=1)
1079    /// ```
1080    ///
1081    /// [BPO 34641]: https://github.com/python/cpython/issues/78822
1082    ParenthesizedKeywordArgumentName,
1083
1084    /// Represents the use of unparenthesized tuple unpacking in a `return` statement or `yield`
1085    /// expression before Python 3.8.
1086    ///
1087    /// ## Examples
1088    ///
1089    /// Before Python 3.8, this syntax was allowed:
1090    ///
1091    /// ```python
1092    /// rest = (4, 5, 6)
1093    ///
1094    /// def f():
1095    ///     t = 1, 2, 3, *rest
1096    ///     return t
1097    ///
1098    /// def g():
1099    ///     t = 1, 2, 3, *rest
1100    ///     yield t
1101    /// ```
1102    ///
1103    /// But this was not:
1104    ///
1105    /// ```python
1106    /// rest = (4, 5, 6)
1107    ///
1108    /// def f():
1109    ///     return 1, 2, 3, *rest
1110    ///
1111    /// def g():
1112    ///     yield 1, 2, 3, *rest
1113    /// ```
1114    ///
1115    /// Instead, parentheses were required in the `return` and `yield` cases:
1116    ///
1117    /// ```python
1118    /// rest = (4, 5, 6)
1119    ///
1120    /// def f():
1121    ///     return (1, 2, 3, *rest)
1122    ///
1123    /// def g():
1124    ///     yield (1, 2, 3, *rest)
1125    /// ```
1126    ///
1127    /// This was reported in [BPO 32117] and updated in Python 3.8 to allow the unparenthesized
1128    /// form.
1129    ///
1130    /// [BPO 32117]: https://github.com/python/cpython/issues/76298
1131    StarTuple(StarTupleKind),
1132
1133    /// Represents the use of a "relaxed" [PEP 614] decorator before Python 3.9.
1134    ///
1135    /// ## Examples
1136    ///
1137    /// Prior to Python 3.9, decorators were defined to be [`dotted_name`]s, optionally followed by
1138    /// an argument list. For example:
1139    ///
1140    /// ```python
1141    /// @buttons.clicked.connect
1142    /// def foo(): ...
1143    ///
1144    /// @buttons.clicked.connect(1, 2, 3)
1145    /// def foo(): ...
1146    /// ```
1147    ///
1148    /// As pointed out in the PEP, this prevented reasonable extensions like subscripts:
1149    ///
1150    /// ```python
1151    /// buttons = [QPushButton(f'Button {i}') for i in range(10)]
1152    ///
1153    /// @buttons[0].clicked.connect
1154    /// def spam(): ...
1155    /// ```
1156    ///
1157    /// Python 3.9 removed these restrictions and expanded the [decorator grammar] to include any
1158    /// assignment expression and include cases like the example above.
1159    ///
1160    /// [PEP 614]: https://peps.python.org/pep-0614/
1161    /// [`dotted_name`]: https://docs.python.org/3.8/reference/compound_stmts.html#grammar-token-dotted-name
1162    /// [decorator grammar]: https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-decorator
1163    RelaxedDecorator(RelaxedDecoratorError),
1164
1165    /// Represents the use of a [PEP 570] positional-only parameter before Python 3.8.
1166    ///
1167    /// ## Examples
1168    ///
1169    /// Python 3.8 added the `/` syntax for marking preceding parameters as positional-only:
1170    ///
1171    /// ```python
1172    /// def foo(a, b, /, c): ...
1173    /// ```
1174    ///
1175    /// This means `a` and `b` in this case can only be provided by position, not by name. In other
1176    /// words, this code results in a `TypeError` at runtime:
1177    ///
1178    /// ```pycon
1179    /// >>> def foo(a, b, /, c): ...
1180    /// ...
1181    /// >>> foo(a=1, b=2, c=3)
1182    /// Traceback (most recent call last):
1183    ///   File "<python-input-3>", line 1, in <module>
1184    ///     foo(a=1, b=2, c=3)
1185    ///     ~~~^^^^^^^^^^^^^^^
1186    /// TypeError: foo() got some positional-only arguments passed as keyword arguments: 'a, b'
1187    /// ```
1188    ///
1189    /// [PEP 570]: https://peps.python.org/pep-0570/
1190    PositionalOnlyParameter,
1191
1192    /// Represents the use of a [type parameter list] before Python 3.12.
1193    ///
1194    /// ## Examples
1195    ///
1196    /// Before Python 3.12, generic parameters had to be declared separately using a class like
1197    /// [`typing.TypeVar`], which could then be used in a function or class definition:
1198    ///
1199    /// ```python
1200    /// from typing import Generic, TypeVar
1201    ///
1202    /// T = TypeVar("T")
1203    ///
1204    /// def f(t: T): ...
1205    /// class C(Generic[T]): ...
1206    /// ```
1207    ///
1208    /// [PEP 695], included in Python 3.12, introduced the new type parameter syntax, which allows
1209    /// these to be written more compactly and without a separate type variable:
1210    ///
1211    /// ```python
1212    /// def f[T](t: T): ...
1213    /// class C[T]: ...
1214    /// ```
1215    ///
1216    /// [type parameter list]: https://docs.python.org/3/reference/compound_stmts.html#type-parameter-lists
1217    /// [PEP 695]: https://peps.python.org/pep-0695/
1218    /// [`typing.TypeVar`]: https://docs.python.org/3/library/typing.html#typevar
1219    TypeParameterList,
1220    LazyImportStatement,
1221    TypeAliasStatement,
1222    TypeParamDefault,
1223
1224    /// Represents the use of a [PEP 701] f-string before Python 3.12.
1225    ///
1226    /// ## Examples
1227    ///
1228    /// As described in the PEP, each of these cases were invalid before Python 3.12:
1229    ///
1230    /// ```python
1231    /// # nested quotes
1232    /// f'Magic wand: { bag['wand'] }'
1233    ///
1234    /// # escape characters
1235    /// f"{'\n'.join(a)}"
1236    ///
1237    /// # comments
1238    /// f'''A complex trick: {
1239    ///     bag['bag']  # recursive bags!
1240    /// }'''
1241    ///
1242    /// # line breaks in a non-triple-quoted replacement field
1243    /// f"{
1244    ///     1
1245    /// }"
1246    ///
1247    /// # arbitrary nesting
1248    /// f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"
1249    /// ```
1250    ///
1251    /// These restrictions were lifted in Python 3.12, meaning that all of these examples are now
1252    /// valid.
1253    ///
1254    /// [PEP 701]: https://peps.python.org/pep-0701/
1255    Pep701FString(FStringKind),
1256
1257    /// Represents the use of a parenthesized `with` item before Python 3.9.
1258    ///
1259    /// ## Examples
1260    ///
1261    /// As described in [BPO 12782], `with` uses like this were not allowed on Python 3.8:
1262    ///
1263    /// ```python
1264    /// with (open("a_really_long_foo") as foo,
1265    ///       open("a_really_long_bar") as bar):
1266    ///     pass
1267    /// ```
1268    ///
1269    /// because parentheses were not allowed within the `with` statement itself (see [this comment]
1270    /// in particular). However, parenthesized expressions were still allowed, including the cases
1271    /// below, so the issue can be pretty subtle and relates specifically to parenthesized items
1272    /// with `as` bindings.
1273    ///
1274    /// ```python
1275    /// with (foo, bar): ...  # okay
1276    /// with (
1277    ///   open('foo.txt')) as foo: ...  # also okay
1278    /// with (
1279    ///   foo,
1280    ///   bar,
1281    ///   baz,
1282    /// ): ...  # also okay, just a tuple
1283    /// with (
1284    ///   foo,
1285    ///   bar,
1286    ///   baz,
1287    /// ) as tup: ...  # also okay, binding the tuple
1288    /// ```
1289    ///
1290    /// This restriction was lifted in 3.9 but formally included in the [release notes] for 3.10.
1291    ///
1292    /// [BPO 12782]: https://github.com/python/cpython/issues/56991
1293    /// [this comment]: https://github.com/python/cpython/issues/56991#issuecomment-1093555141
1294    /// [release notes]: https://docs.python.org/3/whatsnew/3.10.html#summary-release-highlights
1295    ParenthesizedContextManager,
1296
1297    /// Represents the use of a [PEP 646] star expression in an index.
1298    ///
1299    /// ## Examples
1300    ///
1301    /// Before Python 3.11, star expressions were not allowed in index/subscript operations (within
1302    /// square brackets). This restriction was lifted in [PEP 646] to allow for star-unpacking of
1303    /// `typing.TypeVarTuple`s, also added in Python 3.11. As such, this is the primary motivating
1304    /// example from the PEP:
1305    ///
1306    /// ```python
1307    /// from typing import TypeVar, TypeVarTuple
1308    ///
1309    /// DType = TypeVar('DType')
1310    /// Shape = TypeVarTuple('Shape')
1311    ///
1312    /// class Array(Generic[DType, *Shape]): ...
1313    /// ```
1314    ///
1315    /// But it applies to simple indexing as well:
1316    ///
1317    /// ```python
1318    /// vector[*x]
1319    /// array[a, *b]
1320    /// ```
1321    ///
1322    /// [PEP 646]: https://peps.python.org/pep-0646/#change-1-star-expressions-in-indexes
1323    StarExpressionInIndex,
1324
1325    /// Represents the use of a [PEP 646] star annotations in a function definition.
1326    ///
1327    /// ## Examples
1328    ///
1329    /// Before Python 3.11, star annotations were not allowed in function definitions. This
1330    /// restriction was lifted in [PEP 646] to allow type annotations for `typing.TypeVarTuple`,
1331    /// also added in Python 3.11:
1332    ///
1333    /// ```python
1334    /// from typing import TypeVarTuple
1335    ///
1336    /// Ts = TypeVarTuple('Ts')
1337    ///
1338    /// def foo(*args: *Ts): ...
1339    /// ```
1340    ///
1341    /// Unlike [`UnsupportedSyntaxErrorKind::StarExpressionInIndex`], this does not include any
1342    /// other annotation positions:
1343    ///
1344    /// ```python
1345    /// x: *Ts                # Syntax error
1346    /// def foo(x: *Ts): ...  # Syntax error
1347    /// ```
1348    ///
1349    /// [PEP 646]: https://peps.python.org/pep-0646/#change-2-args-as-a-typevartuple
1350    StarAnnotation,
1351
1352    /// Represents the use of iterable or dictionary unpacking inside a comprehension before Python
1353    /// 3.15.
1354    ///
1355    /// ## Examples
1356    ///
1357    /// Before Python 3.15, comprehensions could not use iterable or dictionary unpacking in their
1358    /// element expression:
1359    ///
1360    /// ```python
1361    /// [*x for x in y]  # SyntaxError
1362    /// {*x for x in y}  # SyntaxError
1363    /// (*x for x in y)  # SyntaxError
1364    /// {**d for d in dicts}  # SyntaxError
1365    /// ```
1366    ///
1367    /// Starting with Python 3.15, [PEP 798] allows unpacking within comprehensions:
1368    ///
1369    /// ```python
1370    /// [*x for x in y]
1371    /// {*x for x in y}
1372    /// (*x for x in y)
1373    /// {**d for d in dicts}
1374    /// ```
1375    ///
1376    /// [PEP 798]: https://peps.python.org/pep-0798/
1377    UnpackingInComprehension(ComprehensionUnpackingKind),
1378
1379    /// Represents the use of tuple unpacking in a `for` statement iterator clause before Python
1380    /// 3.9.
1381    ///
1382    /// ## Examples
1383    ///
1384    /// Like [`UnsupportedSyntaxErrorKind::StarTuple`] in `return` and `yield` statements, prior to
1385    /// Python 3.9, tuple unpacking in the iterator clause of a `for` statement required
1386    /// parentheses:
1387    ///
1388    /// ```python
1389    /// # valid on Python 3.8 and earlier
1390    /// for i in (*a, *b): ...
1391    /// ```
1392    ///
1393    /// Omitting the parentheses was invalid:
1394    ///
1395    /// ```python
1396    /// for i in *a, *b: ...  # SyntaxError
1397    /// ```
1398    ///
1399    /// This was changed as part of the [PEG parser rewrite] included in Python 3.9 but not
1400    /// documented directly until the [Python 3.11 release].
1401    ///
1402    /// [PEG parser rewrite]: https://peps.python.org/pep-0617/
1403    /// [Python 3.11 release]: https://docs.python.org/3/whatsnew/3.11.html#other-language-changes
1404    UnparenthesizedUnpackInFor,
1405    /// Represents the use of multiple exception names in an except clause without an `as` binding, before Python 3.14.
1406    ///
1407    /// ## Examples
1408    /// Before Python 3.14, catching multiple exceptions required
1409    /// parentheses like so:
1410    ///
1411    /// ```python
1412    /// try:
1413    ///     ...
1414    /// except (ExceptionA, ExceptionB, ExceptionC):
1415    ///     ...
1416    /// ```
1417    ///
1418    /// Starting with Python 3.14, thanks to [PEP 758], it was permitted
1419    /// to omit the parentheses:
1420    ///
1421    /// ```python
1422    /// try:
1423    ///     ...
1424    /// except ExceptionA, ExceptionB, ExceptionC:
1425    ///     ...
1426    /// ```
1427    ///
1428    /// However, parentheses are still required in the presence of an `as`:
1429    ///
1430    /// ```python
1431    /// try:
1432    ///     ...
1433    /// except (ExceptionA, ExceptionB, ExceptionC) as e:
1434    ///     ...
1435    /// ```
1436    ///
1437    ///
1438    /// [PEP 758]: https://peps.python.org/pep-0758/
1439    UnparenthesizedExceptionTypes,
1440    /// Represents the use of a template string (t-string)
1441    /// literal prior to the implementation of [PEP 750]
1442    /// in Python 3.14.
1443    ///
1444    /// [PEP 750]: https://peps.python.org/pep-0750/
1445    TemplateStrings,
1446
1447    /// Represents the use of a unary plus in a `match` literal pattern before Python 3.15.
1448    ///
1449    /// Before 3.15, unary minus was allowed but not plus:
1450    ///
1451    /// ```python
1452    /// match foo:
1453    ///     case -1: ...  # okay
1454    ///     case +1: ...  # error before 3.15
1455    /// ```
1456    UnaryPlusMatchPattern,
1457}
1458
1459impl Display for UnsupportedSyntaxError {
1460    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1461        let kind = match self.kind {
1462            UnsupportedSyntaxErrorKind::Match => "cannot use `match` statement",
1463            UnsupportedSyntaxErrorKind::Walrus => "cannot use named assignment expression (`:=`)",
1464            UnsupportedSyntaxErrorKind::ExceptStar => "cannot use `except*`",
1465            UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
1466                UnparenthesizedNamedExprKind::SequenceIndex,
1467            ) => "cannot use unparenthesized assignment expression in a sequence index",
1468            UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
1469                UnparenthesizedNamedExprKind::SetLiteral,
1470            ) => "cannot use unparenthesized assignment expression as an element in a set literal",
1471            UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
1472                UnparenthesizedNamedExprKind::SetComprehension,
1473            ) => {
1474                "cannot use unparenthesized assignment expression as an element in a set comprehension"
1475            }
1476            UnsupportedSyntaxErrorKind::ParenthesizedKeywordArgumentName => {
1477                "cannot use parenthesized keyword argument name"
1478            }
1479            UnsupportedSyntaxErrorKind::StarTuple(StarTupleKind::Return) => {
1480                "cannot use iterable unpacking in return statements"
1481            }
1482            UnsupportedSyntaxErrorKind::StarTuple(StarTupleKind::Yield) => {
1483                "cannot use iterable unpacking in yield expressions"
1484            }
1485            UnsupportedSyntaxErrorKind::RelaxedDecorator(relaxed_decorator_error) => {
1486                return match relaxed_decorator_error {
1487                    RelaxedDecoratorError::CallExpression => {
1488                        write!(
1489                            f,
1490                            "cannot use a call expression in a decorator on Python {} \
1491                            unless it is the top-level expression or it occurs \
1492                            in the argument list of a top-level call expression \
1493                            (relaxed decorator syntax was {changed})",
1494                            self.target_version,
1495                            changed = self.kind.changed_version(),
1496                        )
1497                    }
1498                    RelaxedDecoratorError::Other(description) => write!(
1499                        f,
1500                        "cannot use {description} outside function call arguments in a decorator on Python {} \
1501                        (syntax was {changed})",
1502                        self.target_version,
1503                        changed = self.kind.changed_version(),
1504                    ),
1505                };
1506            }
1507            UnsupportedSyntaxErrorKind::PositionalOnlyParameter => {
1508                "cannot use positional-only parameter separator"
1509            }
1510            UnsupportedSyntaxErrorKind::TypeParameterList => "cannot use type parameter lists",
1511            UnsupportedSyntaxErrorKind::LazyImportStatement => "cannot use `lazy` import statement",
1512            UnsupportedSyntaxErrorKind::TypeAliasStatement => "cannot use `type` alias statement",
1513            UnsupportedSyntaxErrorKind::TypeParamDefault => {
1514                "cannot set default type for a type parameter"
1515            }
1516            UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::Backslash) => {
1517                "cannot use an escape sequence (backslash) in f-strings"
1518            }
1519            UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::Comment) => {
1520                "cannot use comments in f-strings"
1521            }
1522            UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::LineBreak) => {
1523                "cannot use line breaks in non-triple-quoted f-string replacement fields"
1524            }
1525            UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::NestedQuote) => {
1526                "cannot reuse outer quote character in f-strings"
1527            }
1528            UnsupportedSyntaxErrorKind::ParenthesizedContextManager => {
1529                "cannot use parentheses within a `with` statement"
1530            }
1531            UnsupportedSyntaxErrorKind::StarExpressionInIndex => {
1532                "cannot use star expression in index"
1533            }
1534            UnsupportedSyntaxErrorKind::StarAnnotation => "cannot use star annotation",
1535            UnsupportedSyntaxErrorKind::UnpackingInComprehension(
1536                ComprehensionUnpackingKind::IterableInList,
1537            ) => "cannot use iterable unpacking in a list comprehension",
1538            UnsupportedSyntaxErrorKind::UnpackingInComprehension(
1539                ComprehensionUnpackingKind::IterableInSet,
1540            ) => "cannot use iterable unpacking in a set comprehension",
1541            UnsupportedSyntaxErrorKind::UnpackingInComprehension(
1542                ComprehensionUnpackingKind::IterableInGenerator,
1543            ) => "cannot use iterable unpacking in a generator expression",
1544            UnsupportedSyntaxErrorKind::UnpackingInComprehension(
1545                ComprehensionUnpackingKind::DictInDict,
1546            ) => "cannot use dictionary unpacking in a dict comprehension",
1547            UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => {
1548                "cannot use iterable unpacking in `for` statements"
1549            }
1550            UnsupportedSyntaxErrorKind::UnparenthesizedExceptionTypes => {
1551                "multiple exception types must be parenthesized"
1552            }
1553            UnsupportedSyntaxErrorKind::TemplateStrings => "cannot use t-strings",
1554            UnsupportedSyntaxErrorKind::UnaryPlusMatchPattern => {
1555                "unary '+' is not allowed in a literal pattern"
1556            }
1557        };
1558
1559        write!(
1560            f,
1561            "{kind} on Python {} (syntax was {changed})",
1562            self.target_version,
1563            changed = self.kind.changed_version(),
1564        )
1565    }
1566}
1567
1568#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
1569pub enum RelaxedDecoratorError {
1570    CallExpression,
1571    Other(&'static str),
1572}
1573
1574/// Represents the kind of change in Python syntax between versions.
1575enum Change {
1576    Added(PythonVersion),
1577    Removed(PythonVersion),
1578}
1579
1580impl Display for Change {
1581    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1582        match self {
1583            Change::Added(version) => write!(f, "added in Python {version}"),
1584            Change::Removed(version) => write!(f, "removed in Python {version}"),
1585        }
1586    }
1587}
1588
1589impl UnsupportedSyntaxErrorKind {
1590    /// Returns the Python version when the syntax associated with this error was changed, and the
1591    /// type of [`Change`] (added or removed).
1592    const fn changed_version(self) -> Change {
1593        match self {
1594            UnsupportedSyntaxErrorKind::Match => Change::Added(PythonVersion::PY310),
1595            UnsupportedSyntaxErrorKind::Walrus => Change::Added(PythonVersion::PY38),
1596            UnsupportedSyntaxErrorKind::ExceptStar => Change::Added(PythonVersion::PY311),
1597            UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(_) => {
1598                Change::Added(PythonVersion::PY39)
1599            }
1600            UnsupportedSyntaxErrorKind::StarTuple(_) => Change::Added(PythonVersion::PY38),
1601            UnsupportedSyntaxErrorKind::RelaxedDecorator { .. } => {
1602                Change::Added(PythonVersion::PY39)
1603            }
1604            UnsupportedSyntaxErrorKind::PositionalOnlyParameter => {
1605                Change::Added(PythonVersion::PY38)
1606            }
1607            UnsupportedSyntaxErrorKind::ParenthesizedKeywordArgumentName => {
1608                Change::Removed(PythonVersion::PY38)
1609            }
1610            UnsupportedSyntaxErrorKind::TypeParameterList => Change::Added(PythonVersion::PY312),
1611            UnsupportedSyntaxErrorKind::LazyImportStatement => Change::Added(PythonVersion::PY315),
1612            UnsupportedSyntaxErrorKind::TypeAliasStatement => Change::Added(PythonVersion::PY312),
1613            UnsupportedSyntaxErrorKind::TypeParamDefault => Change::Added(PythonVersion::PY313),
1614            UnsupportedSyntaxErrorKind::Pep701FString(_) => Change::Added(PythonVersion::PY312),
1615            UnsupportedSyntaxErrorKind::ParenthesizedContextManager => {
1616                Change::Added(PythonVersion::PY39)
1617            }
1618            UnsupportedSyntaxErrorKind::StarExpressionInIndex => {
1619                Change::Added(PythonVersion::PY311)
1620            }
1621            UnsupportedSyntaxErrorKind::StarAnnotation => Change::Added(PythonVersion::PY311),
1622            UnsupportedSyntaxErrorKind::UnpackingInComprehension(_) => {
1623                Change::Added(PythonVersion::PY315)
1624            }
1625            UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => {
1626                Change::Added(PythonVersion::PY39)
1627            }
1628            UnsupportedSyntaxErrorKind::UnparenthesizedExceptionTypes => {
1629                Change::Added(PythonVersion::PY314)
1630            }
1631            UnsupportedSyntaxErrorKind::TemplateStrings => Change::Added(PythonVersion::PY314),
1632            UnsupportedSyntaxErrorKind::UnaryPlusMatchPattern => {
1633                Change::Added(PythonVersion::PY315)
1634            }
1635        }
1636    }
1637
1638    /// Returns whether or not this kind of syntax is unsupported on `target_version`.
1639    pub(crate) fn is_unsupported(self, target_version: PythonVersion) -> bool {
1640        match self.changed_version() {
1641            Change::Added(version) => target_version < version,
1642            Change::Removed(version) => target_version >= version,
1643        }
1644    }
1645
1646    /// Returns `true` if this kind of syntax is supported on `target_version`.
1647    pub(crate) fn is_supported(self, target_version: PythonVersion) -> bool {
1648        !self.is_unsupported(target_version)
1649    }
1650}
1651
1652#[cfg(target_pointer_width = "64")]
1653mod sizes {
1654    use crate::error::{LexicalError, LexicalErrorType};
1655    use static_assertions::assert_eq_size;
1656
1657    assert_eq_size!(LexicalErrorType, [u8; 24]);
1658    assert_eq_size!(LexicalError, [u8; 32]);
1659}