Skip to main content

ruff_python_parser/
error.rs

1use std::fmt::{self, Display};
2
3use ruff_python_ast::PythonVersion;
4use ruff_python_ast::token::TokenKind;
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/// Represents the different types of errors that can occur during parsing of an f-string or t-string.
59#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
60pub enum InterpolatedStringErrorType {
61    /// Expected a right brace after an opened left brace.
62    UnclosedLbrace,
63    /// An invalid conversion flag was encountered.
64    InvalidConversionFlag,
65    /// A single right brace was encountered.
66    SingleRbrace,
67    /// Unterminated string.
68    UnterminatedString,
69    /// Unterminated triple-quoted string.
70    UnterminatedTripleQuotedString,
71    /// A lambda expression without parentheses was encountered.
72    LambdaWithoutParentheses,
73    /// Conversion flag does not immediately follow exclamation.
74    ConversionFlagNotImmediatelyAfterExclamation,
75    /// Newline inside of a format spec for a single quoted f- or t-string.
76    NewlineInFormatSpec,
77}
78
79impl std::fmt::Display for InterpolatedStringErrorType {
80    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
81        match self {
82            Self::UnclosedLbrace => write!(f, "expecting `}}`"),
83            Self::InvalidConversionFlag => write!(f, "invalid conversion character"),
84            Self::SingleRbrace => write!(f, "single `}}` is not allowed"),
85            Self::UnterminatedString => write!(f, "unterminated string"),
86            Self::UnterminatedTripleQuotedString => write!(f, "unterminated triple-quoted string"),
87            Self::LambdaWithoutParentheses => {
88                write!(f, "lambda expressions are not allowed without parentheses")
89            }
90            Self::ConversionFlagNotImmediatelyAfterExclamation => write!(
91                f,
92                "conversion type must come right after the exclamation mark"
93            ),
94            Self::NewlineInFormatSpec => {
95                write!(
96                    f,
97                    "newlines are not allowed in format specifiers when using single quotes"
98                )
99            }
100        }
101    }
102}
103
104/// Represents the different types of errors that can occur during parsing.
105#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize)]
106pub enum ParseErrorType {
107    /// An unexpected error occurred.
108    OtherError(String),
109
110    /// An error specific to stringified annotations occurred.
111    StringAnnotationError(&'static str),
112
113    /// An empty slice was found during parsing, e.g `data[]`.
114    EmptySlice,
115    /// An empty global names list was found during parsing.
116    EmptyGlobalNames,
117    /// An empty nonlocal names list was found during parsing.
118    EmptyNonlocalNames,
119    /// An empty delete targets list was found during parsing.
120    EmptyDeleteTargets,
121    /// An empty import names list was found during parsing.
122    EmptyImportNames,
123    /// An empty type parameter list was found during parsing.
124    EmptyTypeParams,
125
126    /// An unparenthesized named expression was found where it is not allowed.
127    UnparenthesizedNamedExpression,
128    /// An unparenthesized tuple expression was found where it is not allowed.
129    UnparenthesizedTupleExpression,
130    /// An unparenthesized generator expression was found where it is not allowed.
131    UnparenthesizedGeneratorExpression,
132
133    /// An invalid usage of a lambda expression was found.
134    InvalidLambdaExpressionUsage,
135    /// An invalid usage of a yield expression was found.
136    InvalidYieldExpressionUsage,
137    /// An invalid usage of a starred expression was found.
138    InvalidStarredExpressionUsage,
139    /// A star pattern was found outside a sequence pattern.
140    InvalidStarPatternUsage,
141    /// An underscore was used as a binding target in a match pattern.
142    InvalidMatchPatternTarget,
143
144    /// A parameter was found after a vararg.
145    ParamAfterVarKeywordParam,
146    /// A non-default parameter follows a default parameter.
147    NonDefaultParamAfterDefaultParam,
148    /// A default value was found for a `*` or `**` parameter.
149    VarParameterWithDefault,
150
151    /// An invalid expression was found in the assignment target.
152    InvalidAssignmentTarget,
153    /// An invalid expression was found in the named assignment target.
154    InvalidNamedAssignmentTarget,
155    /// An invalid expression was found in the annotated assignment target.
156    InvalidAnnotatedAssignmentTarget,
157    /// An invalid expression was found in the augmented assignment target.
158    InvalidAugmentedAssignmentTarget,
159    /// An invalid expression was found in the delete target.
160    InvalidDeleteTarget,
161
162    /// A positional argument was found after a keyword argument.
163    PositionalAfterKeywordArgument,
164    /// A positional argument was found after a keyword argument unpacking.
165    PositionalAfterKeywordUnpacking,
166    /// An iterable argument unpacking was found after keyword argument unpacking.
167    InvalidArgumentUnpackingOrder,
168    /// An invalid usage of iterable unpacking in a comprehension was found.
169    IterableUnpackingInComprehension,
170
171    /// Multiple simple statements were found in the same line without a `;` separating them.
172    SimpleStatementsOnSameLine,
173    /// A simple statement and a compound statement was found in the same line.
174    SimpleAndCompoundStatementOnSameLine,
175
176    /// Expected one or more keyword parameter after `*` separator.
177    ExpectedKeywordParam,
178    /// Expected a real number for a complex literal pattern.
179    ExpectedRealNumber,
180    /// Expected an imaginary number for a complex literal pattern.
181    ExpectedImaginaryNumber,
182    /// Expected an expression at the current parser location.
183    ExpectedExpression,
184    /// The parser expected a specific token that was not found.
185    ExpectedToken {
186        expected: TokenKind,
187        found: TokenKind,
188    },
189
190    /// An unexpected indentation was found during parsing.
191    UnexpectedIndentation,
192    /// The statement being parsed cannot be `async`.
193    UnexpectedTokenAfterAsync(TokenKind),
194    /// Ipython escape command was found
195    UnexpectedIpythonEscapeCommand,
196    /// An unexpected token was found at the end of an expression parsing
197    UnexpectedExpressionToken,
198
199    /// An f-string error containing the [`InterpolatedStringErrorType`].
200    FStringError(InterpolatedStringErrorType),
201    /// A t-string error containing the [`InterpolatedStringErrorType`].
202    TStringError(InterpolatedStringErrorType),
203    /// Parser encountered an error during lexing.
204    Lexical(LexicalErrorType),
205}
206
207impl ParseErrorType {
208    pub(crate) fn from_interpolated_string_error(
209        error: InterpolatedStringErrorType,
210        string_kind: InterpolatedStringKind,
211    ) -> Self {
212        match string_kind {
213            InterpolatedStringKind::FString => Self::FStringError(error),
214            InterpolatedStringKind::TString => Self::TStringError(error),
215        }
216    }
217}
218
219impl std::error::Error for ParseErrorType {}
220
221impl std::fmt::Display for ParseErrorType {
222    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
223        match self {
224            ParseErrorType::OtherError(msg) => f.write_str(msg),
225            ParseErrorType::StringAnnotationError(msg) => f.write_str(msg),
226            ParseErrorType::ExpectedToken { found, expected } => {
227                write!(f, "Expected {expected}, found {found}")
228            }
229            ParseErrorType::Lexical(lex_error) => write!(f, "{lex_error}"),
230            ParseErrorType::SimpleStatementsOnSameLine => {
231                f.write_str("Simple statements must be separated by newlines or semicolons")
232            }
233            ParseErrorType::SimpleAndCompoundStatementOnSameLine => f.write_str(
234                "Compound statements are not allowed on the same line as simple statements",
235            ),
236            ParseErrorType::UnexpectedTokenAfterAsync(kind) => {
237                write!(
238                    f,
239                    "Expected `def`, `with` or `for` to follow `async`, found {kind}",
240                )
241            }
242            ParseErrorType::InvalidArgumentUnpackingOrder => {
243                f.write_str("Iterable argument unpacking cannot follow keyword argument unpacking")
244            }
245            ParseErrorType::IterableUnpackingInComprehension => {
246                f.write_str("Iterable unpacking cannot be used in a comprehension")
247            }
248            ParseErrorType::UnparenthesizedNamedExpression => {
249                f.write_str("Unparenthesized named expression cannot be used here")
250            }
251            ParseErrorType::UnparenthesizedTupleExpression => {
252                f.write_str("Unparenthesized tuple expression cannot be used here")
253            }
254            ParseErrorType::UnparenthesizedGeneratorExpression => {
255                f.write_str("Unparenthesized generator expression cannot be used here")
256            }
257            ParseErrorType::InvalidYieldExpressionUsage => {
258                f.write_str("Yield expression cannot be used here")
259            }
260            ParseErrorType::InvalidLambdaExpressionUsage => {
261                f.write_str("Lambda expression cannot be used here")
262            }
263            ParseErrorType::InvalidStarredExpressionUsage => {
264                f.write_str("Starred expression cannot be used here")
265            }
266            ParseErrorType::PositionalAfterKeywordArgument => {
267                f.write_str("Positional argument cannot follow keyword argument")
268            }
269            ParseErrorType::PositionalAfterKeywordUnpacking => {
270                f.write_str("Positional argument cannot follow keyword argument unpacking")
271            }
272            ParseErrorType::EmptySlice => f.write_str("Expected index or slice expression"),
273            ParseErrorType::EmptyGlobalNames => {
274                f.write_str("Global statement must have at least one name")
275            }
276            ParseErrorType::EmptyNonlocalNames => {
277                f.write_str("Nonlocal statement must have at least one name")
278            }
279            ParseErrorType::EmptyDeleteTargets => {
280                f.write_str("Delete statement must have at least one target")
281            }
282            ParseErrorType::EmptyImportNames => {
283                f.write_str("Expected one or more symbol names after import")
284            }
285            ParseErrorType::EmptyTypeParams => f.write_str("Type parameter list cannot be empty"),
286            ParseErrorType::ParamAfterVarKeywordParam => {
287                f.write_str("Parameter cannot follow var-keyword parameter")
288            }
289            ParseErrorType::NonDefaultParamAfterDefaultParam => {
290                f.write_str("Parameter without a default cannot follow a parameter with a default")
291            }
292            ParseErrorType::ExpectedKeywordParam => {
293                f.write_str("Expected one or more keyword parameter after `*` separator")
294            }
295            ParseErrorType::VarParameterWithDefault => {
296                f.write_str("Parameter with `*` or `**` cannot have default value")
297            }
298            ParseErrorType::InvalidStarPatternUsage => {
299                f.write_str("Star pattern cannot be used here")
300            }
301            ParseErrorType::InvalidMatchPatternTarget => f.write_str("cannot use '_' as a target"),
302            ParseErrorType::ExpectedRealNumber => {
303                f.write_str("Expected a real number in complex literal pattern")
304            }
305            ParseErrorType::ExpectedImaginaryNumber => {
306                f.write_str("Expected an imaginary number in complex literal pattern")
307            }
308            ParseErrorType::ExpectedExpression => f.write_str("Expected an expression"),
309            ParseErrorType::UnexpectedIndentation => f.write_str("Unexpected indentation"),
310            ParseErrorType::InvalidAssignmentTarget => f.write_str("Invalid assignment target"),
311            ParseErrorType::InvalidAnnotatedAssignmentTarget => {
312                f.write_str("Invalid annotated assignment target")
313            }
314            ParseErrorType::InvalidNamedAssignmentTarget => {
315                f.write_str("Assignment expression target must be an identifier")
316            }
317            ParseErrorType::InvalidAugmentedAssignmentTarget => {
318                f.write_str("Invalid augmented assignment target")
319            }
320            ParseErrorType::InvalidDeleteTarget => f.write_str("Invalid delete target"),
321            ParseErrorType::UnexpectedIpythonEscapeCommand => {
322                f.write_str("IPython escape commands are only allowed in `Mode::Ipython`")
323            }
324            ParseErrorType::FStringError(fstring_error) => {
325                write!(f, "f-string: {fstring_error}")
326            }
327            ParseErrorType::TStringError(tstring_error) => {
328                write!(f, "t-string: {tstring_error}")
329            }
330            ParseErrorType::UnexpectedExpressionToken => {
331                write!(f, "Unexpected token at the end of an expression")
332            }
333        }
334    }
335}
336
337/// Represents an error that occur during lexing and are
338/// returned by the `parse_*` functions in the iterator in the
339/// [lexer] implementation.
340///
341/// [lexer]: crate::lexer
342#[derive(Debug, Clone, PartialEq)]
343pub struct LexicalError {
344    /// The type of error that occurred.
345    error: LexicalErrorType,
346    /// The location of the error.
347    location: TextRange,
348}
349
350impl LexicalError {
351    /// Creates a new `LexicalError` with the given error type and location.
352    pub fn new(error: LexicalErrorType, location: TextRange) -> Self {
353        Self { error, location }
354    }
355
356    pub fn error(&self) -> &LexicalErrorType {
357        &self.error
358    }
359
360    pub fn into_error(self) -> LexicalErrorType {
361        self.error
362    }
363
364    pub fn location(&self) -> TextRange {
365        self.location
366    }
367}
368
369impl std::ops::Deref for LexicalError {
370    type Target = LexicalErrorType;
371
372    fn deref(&self) -> &Self::Target {
373        self.error()
374    }
375}
376
377impl std::error::Error for LexicalError {
378    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
379        Some(self.error())
380    }
381}
382
383impl std::fmt::Display for LexicalError {
384    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
385        write!(
386            f,
387            "{} at byte offset {}",
388            self.error(),
389            u32::from(self.location().start())
390        )
391    }
392}
393
394/// Represents the different types of errors that can occur during lexing.
395#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
396pub enum LexicalErrorType {
397    // TODO: Can probably be removed, the places it is used seem to be able
398    // to use the `UnicodeError` variant instead.
399    #[doc(hidden)]
400    StringError,
401    /// A string literal without the closing quote.
402    UnclosedStringError,
403    /// Decoding of a unicode escape sequence in a string literal failed.
404    UnicodeError,
405    /// Missing the `{` for unicode escape sequence.
406    MissingUnicodeLbrace,
407    /// Missing the `}` for unicode escape sequence.
408    MissingUnicodeRbrace,
409    /// The indentation is not consistent.
410    IndentationError,
411    /// An unrecognized token was encountered.
412    UnrecognizedToken { tok: char },
413    /// An f-string error containing the [`InterpolatedStringErrorType`].
414    FStringError(InterpolatedStringErrorType),
415    /// A t-string error containing the [`InterpolatedStringErrorType`].
416    TStringError(InterpolatedStringErrorType),
417    /// Invalid character encountered in a byte literal.
418    InvalidByteLiteral,
419    /// An unexpected character was encountered after a line continuation.
420    LineContinuationError,
421    /// An unexpected end of file was encountered.
422    Eof,
423    /// An unexpected error occurred.
424    OtherError(Box<str>),
425}
426
427impl std::error::Error for LexicalErrorType {}
428
429impl LexicalErrorType {
430    pub(crate) fn from_interpolated_string_error(
431        error: InterpolatedStringErrorType,
432        string_kind: InterpolatedStringKind,
433    ) -> Self {
434        match string_kind {
435            InterpolatedStringKind::FString => Self::FStringError(error),
436            InterpolatedStringKind::TString => Self::TStringError(error),
437        }
438    }
439}
440
441impl std::fmt::Display for LexicalErrorType {
442    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
443        match self {
444            Self::StringError => write!(f, "Got unexpected string"),
445            Self::FStringError(error) => write!(f, "f-string: {error}"),
446            Self::TStringError(error) => write!(f, "t-string: {error}"),
447            Self::InvalidByteLiteral => {
448                write!(f, "bytes can only contain ASCII literal characters")
449            }
450            Self::UnicodeError => write!(f, "Got unexpected unicode"),
451            Self::IndentationError => {
452                write!(f, "unindent does not match any outer indentation level")
453            }
454            Self::UnrecognizedToken { tok } => {
455                write!(f, "Got unexpected token {tok}")
456            }
457            Self::LineContinuationError => {
458                write!(f, "Expected a newline after line continuation character")
459            }
460            Self::Eof => write!(f, "unexpected EOF while parsing"),
461            Self::OtherError(msg) => write!(f, "{msg}"),
462            Self::UnclosedStringError => {
463                write!(f, "missing closing quote in string literal")
464            }
465            Self::MissingUnicodeLbrace => {
466                write!(f, "Missing `{{` in Unicode escape sequence")
467            }
468            Self::MissingUnicodeRbrace => {
469                write!(f, "Missing `}}` in Unicode escape sequence")
470            }
471        }
472    }
473}
474
475/// Represents a version-related syntax error detected during parsing.
476///
477/// An example of a version-related error is the use of a `match` statement before Python 3.10, when
478/// it was first introduced. See [`UnsupportedSyntaxErrorKind`] for other kinds of errors.
479#[derive(Debug, PartialEq, Clone, get_size2::GetSize)]
480pub struct UnsupportedSyntaxError {
481    pub kind: UnsupportedSyntaxErrorKind,
482    pub range: TextRange,
483    /// The target [`PythonVersion`] for which this error was detected.
484    pub target_version: PythonVersion,
485}
486
487impl Ranged for UnsupportedSyntaxError {
488    fn range(&self) -> TextRange {
489        self.range
490    }
491}
492
493/// The type of tuple unpacking for [`UnsupportedSyntaxErrorKind::StarTuple`].
494#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, get_size2::GetSize)]
495pub enum StarTupleKind {
496    Return,
497    Yield,
498}
499
500/// The type of PEP 701 f-string error for [`UnsupportedSyntaxErrorKind::Pep701FString`].
501#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, get_size2::GetSize)]
502pub enum FStringKind {
503    Backslash,
504    Comment,
505    LineBreak,
506    NestedQuote,
507}
508
509/// The type of PEP 798 unpacking-comprehension error for
510/// [`UnsupportedSyntaxErrorKind::UnpackingInComprehension`].
511#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, get_size2::GetSize)]
512pub enum ComprehensionUnpackingKind {
513    IterableInList,
514    IterableInSet,
515    IterableInGenerator,
516    DictInDict,
517}
518
519#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, get_size2::GetSize)]
520pub enum UnparenthesizedNamedExprKind {
521    SequenceIndex,
522    SetLiteral,
523    SetComprehension,
524}
525
526#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, get_size2::GetSize)]
527pub enum UnsupportedSyntaxErrorKind {
528    Match,
529    Walrus,
530    ExceptStar,
531    /// Represents the use of an unparenthesized named expression (`:=`) in a set literal, set
532    /// comprehension, or sequence index before Python 3.10.
533    ///
534    /// ## Examples
535    ///
536    /// These are allowed on Python 3.10:
537    ///
538    /// ```python
539    /// {x := 1, 2, 3}                 # set literal
540    /// {last := x for x in range(3)}  # set comprehension
541    /// lst[x := 1]                    # sequence index
542    /// ```
543    ///
544    /// But on Python 3.9 the named expression needs to be parenthesized:
545    ///
546    /// ```python
547    /// {(x := 1), 2, 3}                 # set literal
548    /// {(last := x) for x in range(3)}  # set comprehension
549    /// lst[(x := 1)]                    # sequence index
550    /// ```
551    ///
552    /// However, unparenthesized named expressions are never allowed in slices:
553    ///
554    /// ```python
555    /// lst[x:=1:-1]      # syntax error
556    /// lst[1:x:=1]       # syntax error
557    /// lst[1:3:x:=1]     # syntax error
558    ///
559    /// lst[(x:=1):-1]    # ok
560    /// lst[1:(x:=1)]     # ok
561    /// lst[1:3:(x:=1)]   # ok
562    /// ```
563    ///
564    /// ## References
565    ///
566    /// - [Python 3.10 Other Language Changes](https://docs.python.org/3/whatsnew/3.10.html#other-language-changes)
567    UnparenthesizedNamedExpr(UnparenthesizedNamedExprKind),
568
569    /// Represents the use of a parenthesized keyword argument name after Python 3.8.
570    ///
571    /// ## Example
572    ///
573    /// From [BPO 34641] it sounds like this was only accidentally supported and was removed when
574    /// noticed. Code like this used to be valid:
575    ///
576    /// ```python
577    /// f((a)=1)
578    /// ```
579    ///
580    /// After Python 3.8, you have to omit the parentheses around `a`:
581    ///
582    /// ```python
583    /// f(a=1)
584    /// ```
585    ///
586    /// [BPO 34641]: https://github.com/python/cpython/issues/78822
587    ParenthesizedKeywordArgumentName,
588
589    /// Represents the use of unparenthesized tuple unpacking in a `return` statement or `yield`
590    /// expression before Python 3.8.
591    ///
592    /// ## Examples
593    ///
594    /// Before Python 3.8, this syntax was allowed:
595    ///
596    /// ```python
597    /// rest = (4, 5, 6)
598    ///
599    /// def f():
600    ///     t = 1, 2, 3, *rest
601    ///     return t
602    ///
603    /// def g():
604    ///     t = 1, 2, 3, *rest
605    ///     yield t
606    /// ```
607    ///
608    /// But this was not:
609    ///
610    /// ```python
611    /// rest = (4, 5, 6)
612    ///
613    /// def f():
614    ///     return 1, 2, 3, *rest
615    ///
616    /// def g():
617    ///     yield 1, 2, 3, *rest
618    /// ```
619    ///
620    /// Instead, parentheses were required in the `return` and `yield` cases:
621    ///
622    /// ```python
623    /// rest = (4, 5, 6)
624    ///
625    /// def f():
626    ///     return (1, 2, 3, *rest)
627    ///
628    /// def g():
629    ///     yield (1, 2, 3, *rest)
630    /// ```
631    ///
632    /// This was reported in [BPO 32117] and updated in Python 3.8 to allow the unparenthesized
633    /// form.
634    ///
635    /// [BPO 32117]: https://github.com/python/cpython/issues/76298
636    StarTuple(StarTupleKind),
637
638    /// Represents the use of a "relaxed" [PEP 614] decorator before Python 3.9.
639    ///
640    /// ## Examples
641    ///
642    /// Prior to Python 3.9, decorators were defined to be [`dotted_name`]s, optionally followed by
643    /// an argument list. For example:
644    ///
645    /// ```python
646    /// @buttons.clicked.connect
647    /// def foo(): ...
648    ///
649    /// @buttons.clicked.connect(1, 2, 3)
650    /// def foo(): ...
651    /// ```
652    ///
653    /// As pointed out in the PEP, this prevented reasonable extensions like subscripts:
654    ///
655    /// ```python
656    /// buttons = [QPushButton(f'Button {i}') for i in range(10)]
657    ///
658    /// @buttons[0].clicked.connect
659    /// def spam(): ...
660    /// ```
661    ///
662    /// Python 3.9 removed these restrictions and expanded the [decorator grammar] to include any
663    /// assignment expression and include cases like the example above.
664    ///
665    /// [PEP 614]: https://peps.python.org/pep-0614/
666    /// [`dotted_name`]: https://docs.python.org/3.8/reference/compound_stmts.html#grammar-token-dotted-name
667    /// [decorator grammar]: https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-decorator
668    RelaxedDecorator(RelaxedDecoratorError),
669
670    /// Represents the use of a [PEP 570] positional-only parameter before Python 3.8.
671    ///
672    /// ## Examples
673    ///
674    /// Python 3.8 added the `/` syntax for marking preceding parameters as positional-only:
675    ///
676    /// ```python
677    /// def foo(a, b, /, c): ...
678    /// ```
679    ///
680    /// This means `a` and `b` in this case can only be provided by position, not by name. In other
681    /// words, this code results in a `TypeError` at runtime:
682    ///
683    /// ```pycon
684    /// >>> def foo(a, b, /, c): ...
685    /// ...
686    /// >>> foo(a=1, b=2, c=3)
687    /// Traceback (most recent call last):
688    ///   File "<python-input-3>", line 1, in <module>
689    ///     foo(a=1, b=2, c=3)
690    ///     ~~~^^^^^^^^^^^^^^^
691    /// TypeError: foo() got some positional-only arguments passed as keyword arguments: 'a, b'
692    /// ```
693    ///
694    /// [PEP 570]: https://peps.python.org/pep-0570/
695    PositionalOnlyParameter,
696
697    /// Represents the use of a [type parameter list] before Python 3.12.
698    ///
699    /// ## Examples
700    ///
701    /// Before Python 3.12, generic parameters had to be declared separately using a class like
702    /// [`typing.TypeVar`], which could then be used in a function or class definition:
703    ///
704    /// ```python
705    /// from typing import Generic, TypeVar
706    ///
707    /// T = TypeVar("T")
708    ///
709    /// def f(t: T): ...
710    /// class C(Generic[T]): ...
711    /// ```
712    ///
713    /// [PEP 695], included in Python 3.12, introduced the new type parameter syntax, which allows
714    /// these to be written more compactly and without a separate type variable:
715    ///
716    /// ```python
717    /// def f[T](t: T): ...
718    /// class C[T]: ...
719    /// ```
720    ///
721    /// [type parameter list]: https://docs.python.org/3/reference/compound_stmts.html#type-parameter-lists
722    /// [PEP 695]: https://peps.python.org/pep-0695/
723    /// [`typing.TypeVar`]: https://docs.python.org/3/library/typing.html#typevar
724    TypeParameterList,
725    LazyImportStatement,
726    TypeAliasStatement,
727    TypeParamDefault,
728
729    /// Represents the use of a [PEP 701] f-string before Python 3.12.
730    ///
731    /// ## Examples
732    ///
733    /// As described in the PEP, each of these cases were invalid before Python 3.12:
734    ///
735    /// ```python
736    /// # nested quotes
737    /// f'Magic wand: { bag['wand'] }'
738    ///
739    /// # escape characters
740    /// f"{'\n'.join(a)}"
741    ///
742    /// # comments
743    /// f'''A complex trick: {
744    ///     bag['bag']  # recursive bags!
745    /// }'''
746    ///
747    /// # line breaks in a non-triple-quoted replacement field
748    /// f"{
749    ///     1
750    /// }"
751    ///
752    /// # arbitrary nesting
753    /// f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"
754    /// ```
755    ///
756    /// These restrictions were lifted in Python 3.12, meaning that all of these examples are now
757    /// valid.
758    ///
759    /// [PEP 701]: https://peps.python.org/pep-0701/
760    Pep701FString(FStringKind),
761
762    /// Represents the use of a parenthesized `with` item before Python 3.9.
763    ///
764    /// ## Examples
765    ///
766    /// As described in [BPO 12782], `with` uses like this were not allowed on Python 3.8:
767    ///
768    /// ```python
769    /// with (open("a_really_long_foo") as foo,
770    ///       open("a_really_long_bar") as bar):
771    ///     pass
772    /// ```
773    ///
774    /// because parentheses were not allowed within the `with` statement itself (see [this comment]
775    /// in particular). However, parenthesized expressions were still allowed, including the cases
776    /// below, so the issue can be pretty subtle and relates specifically to parenthesized items
777    /// with `as` bindings.
778    ///
779    /// ```python
780    /// with (foo, bar): ...  # okay
781    /// with (
782    ///   open('foo.txt')) as foo: ...  # also okay
783    /// with (
784    ///   foo,
785    ///   bar,
786    ///   baz,
787    /// ): ...  # also okay, just a tuple
788    /// with (
789    ///   foo,
790    ///   bar,
791    ///   baz,
792    /// ) as tup: ...  # also okay, binding the tuple
793    /// ```
794    ///
795    /// This restriction was lifted in 3.9 but formally included in the [release notes] for 3.10.
796    ///
797    /// [BPO 12782]: https://github.com/python/cpython/issues/56991
798    /// [this comment]: https://github.com/python/cpython/issues/56991#issuecomment-1093555141
799    /// [release notes]: https://docs.python.org/3/whatsnew/3.10.html#summary-release-highlights
800    ParenthesizedContextManager,
801
802    /// Represents the use of a [PEP 646] star expression in an index.
803    ///
804    /// ## Examples
805    ///
806    /// Before Python 3.11, star expressions were not allowed in index/subscript operations (within
807    /// square brackets). This restriction was lifted in [PEP 646] to allow for star-unpacking of
808    /// `typing.TypeVarTuple`s, also added in Python 3.11. As such, this is the primary motivating
809    /// example from the PEP:
810    ///
811    /// ```python
812    /// from typing import TypeVar, TypeVarTuple
813    ///
814    /// DType = TypeVar('DType')
815    /// Shape = TypeVarTuple('Shape')
816    ///
817    /// class Array(Generic[DType, *Shape]): ...
818    /// ```
819    ///
820    /// But it applies to simple indexing as well:
821    ///
822    /// ```python
823    /// vector[*x]
824    /// array[a, *b]
825    /// ```
826    ///
827    /// [PEP 646]: https://peps.python.org/pep-0646/#change-1-star-expressions-in-indexes
828    StarExpressionInIndex,
829
830    /// Represents the use of a [PEP 646] star annotations in a function definition.
831    ///
832    /// ## Examples
833    ///
834    /// Before Python 3.11, star annotations were not allowed in function definitions. This
835    /// restriction was lifted in [PEP 646] to allow type annotations for `typing.TypeVarTuple`,
836    /// also added in Python 3.11:
837    ///
838    /// ```python
839    /// from typing import TypeVarTuple
840    ///
841    /// Ts = TypeVarTuple('Ts')
842    ///
843    /// def foo(*args: *Ts): ...
844    /// ```
845    ///
846    /// Unlike [`UnsupportedSyntaxErrorKind::StarExpressionInIndex`], this does not include any
847    /// other annotation positions:
848    ///
849    /// ```python
850    /// x: *Ts                # Syntax error
851    /// def foo(x: *Ts): ...  # Syntax error
852    /// ```
853    ///
854    /// [PEP 646]: https://peps.python.org/pep-0646/#change-2-args-as-a-typevartuple
855    StarAnnotation,
856
857    /// Represents the use of iterable or dictionary unpacking inside a comprehension before Python
858    /// 3.15.
859    ///
860    /// ## Examples
861    ///
862    /// Before Python 3.15, comprehensions could not use iterable or dictionary unpacking in their
863    /// element expression:
864    ///
865    /// ```python
866    /// [*x for x in y]  # SyntaxError
867    /// {*x for x in y}  # SyntaxError
868    /// (*x for x in y)  # SyntaxError
869    /// {**d for d in dicts}  # SyntaxError
870    /// ```
871    ///
872    /// Starting with Python 3.15, [PEP 798] allows unpacking within comprehensions:
873    ///
874    /// ```python
875    /// [*x for x in y]
876    /// {*x for x in y}
877    /// (*x for x in y)
878    /// {**d for d in dicts}
879    /// ```
880    ///
881    /// [PEP 798]: https://peps.python.org/pep-0798/
882    UnpackingInComprehension(ComprehensionUnpackingKind),
883
884    /// Represents the use of tuple unpacking in a `for` statement iterator clause before Python
885    /// 3.9.
886    ///
887    /// ## Examples
888    ///
889    /// Like [`UnsupportedSyntaxErrorKind::StarTuple`] in `return` and `yield` statements, prior to
890    /// Python 3.9, tuple unpacking in the iterator clause of a `for` statement required
891    /// parentheses:
892    ///
893    /// ```python
894    /// # valid on Python 3.8 and earlier
895    /// for i in (*a, *b): ...
896    /// ```
897    ///
898    /// Omitting the parentheses was invalid:
899    ///
900    /// ```python
901    /// for i in *a, *b: ...  # SyntaxError
902    /// ```
903    ///
904    /// This was changed as part of the [PEG parser rewrite] included in Python 3.9 but not
905    /// documented directly until the [Python 3.11 release].
906    ///
907    /// [PEG parser rewrite]: https://peps.python.org/pep-0617/
908    /// [Python 3.11 release]: https://docs.python.org/3/whatsnew/3.11.html#other-language-changes
909    UnparenthesizedUnpackInFor,
910    /// Represents the use of multiple exception names in an except clause without an `as` binding, before Python 3.14.
911    ///
912    /// ## Examples
913    /// Before Python 3.14, catching multiple exceptions required
914    /// parentheses like so:
915    ///
916    /// ```python
917    /// try:
918    ///     ...
919    /// except (ExceptionA, ExceptionB, ExceptionC):
920    ///     ...
921    /// ```
922    ///
923    /// Starting with Python 3.14, thanks to [PEP 758], it was permitted
924    /// to omit the parentheses:
925    ///
926    /// ```python
927    /// try:
928    ///     ...
929    /// except ExceptionA, ExceptionB, ExceptionC:
930    ///     ...
931    /// ```
932    ///
933    /// However, parentheses are still required in the presence of an `as`:
934    ///
935    /// ```python
936    /// try:
937    ///     ...
938    /// except (ExceptionA, ExceptionB, ExceptionC) as e:
939    ///     ...
940    /// ```
941    ///
942    ///
943    /// [PEP 758]: https://peps.python.org/pep-0758/
944    UnparenthesizedExceptionTypes,
945    /// Represents the use of a template string (t-string)
946    /// literal prior to the implementation of [PEP 750]
947    /// in Python 3.14.
948    ///
949    /// [PEP 750]: https://peps.python.org/pep-0750/
950    TemplateStrings,
951}
952
953impl Display for UnsupportedSyntaxError {
954    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
955        let kind = match self.kind {
956            UnsupportedSyntaxErrorKind::Match => "Cannot use `match` statement",
957            UnsupportedSyntaxErrorKind::Walrus => "Cannot use named assignment expression (`:=`)",
958            UnsupportedSyntaxErrorKind::ExceptStar => "Cannot use `except*`",
959            UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
960                UnparenthesizedNamedExprKind::SequenceIndex,
961            ) => "Cannot use unparenthesized assignment expression in a sequence index",
962            UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
963                UnparenthesizedNamedExprKind::SetLiteral,
964            ) => "Cannot use unparenthesized assignment expression as an element in a set literal",
965            UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
966                UnparenthesizedNamedExprKind::SetComprehension,
967            ) => {
968                "Cannot use unparenthesized assignment expression as an element in a set comprehension"
969            }
970            UnsupportedSyntaxErrorKind::ParenthesizedKeywordArgumentName => {
971                "Cannot use parenthesized keyword argument name"
972            }
973            UnsupportedSyntaxErrorKind::StarTuple(StarTupleKind::Return) => {
974                "Cannot use iterable unpacking in return statements"
975            }
976            UnsupportedSyntaxErrorKind::StarTuple(StarTupleKind::Yield) => {
977                "Cannot use iterable unpacking in yield expressions"
978            }
979            UnsupportedSyntaxErrorKind::RelaxedDecorator(relaxed_decorator_error) => {
980                return match relaxed_decorator_error {
981                    RelaxedDecoratorError::CallExpression => {
982                        write!(
983                            f,
984                            "Cannot use a call expression in a decorator on Python {} \
985                            unless it is the top-level expression or it occurs \
986                            in the argument list of a top-level call expression \
987                            (relaxed decorator syntax was {changed})",
988                            self.target_version,
989                            changed = self.kind.changed_version(),
990                        )
991                    }
992                    RelaxedDecoratorError::Other(description) => write!(
993                        f,
994                        "Cannot use {description} outside function call arguments in a decorator on Python {} \
995                        (syntax was {changed})",
996                        self.target_version,
997                        changed = self.kind.changed_version(),
998                    ),
999                };
1000            }
1001            UnsupportedSyntaxErrorKind::PositionalOnlyParameter => {
1002                "Cannot use positional-only parameter separator"
1003            }
1004            UnsupportedSyntaxErrorKind::TypeParameterList => "Cannot use type parameter lists",
1005            UnsupportedSyntaxErrorKind::LazyImportStatement => "Cannot use `lazy` import statement",
1006            UnsupportedSyntaxErrorKind::TypeAliasStatement => "Cannot use `type` alias statement",
1007            UnsupportedSyntaxErrorKind::TypeParamDefault => {
1008                "Cannot set default type for a type parameter"
1009            }
1010            UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::Backslash) => {
1011                "Cannot use an escape sequence (backslash) in f-strings"
1012            }
1013            UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::Comment) => {
1014                "Cannot use comments in f-strings"
1015            }
1016            UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::LineBreak) => {
1017                "Cannot use line breaks in non-triple-quoted f-string replacement fields"
1018            }
1019            UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::NestedQuote) => {
1020                "Cannot reuse outer quote character in f-strings"
1021            }
1022            UnsupportedSyntaxErrorKind::ParenthesizedContextManager => {
1023                "Cannot use parentheses within a `with` statement"
1024            }
1025            UnsupportedSyntaxErrorKind::StarExpressionInIndex => {
1026                "Cannot use star expression in index"
1027            }
1028            UnsupportedSyntaxErrorKind::StarAnnotation => "Cannot use star annotation",
1029            UnsupportedSyntaxErrorKind::UnpackingInComprehension(
1030                ComprehensionUnpackingKind::IterableInList,
1031            ) => "Cannot use iterable unpacking in a list comprehension",
1032            UnsupportedSyntaxErrorKind::UnpackingInComprehension(
1033                ComprehensionUnpackingKind::IterableInSet,
1034            ) => "Cannot use iterable unpacking in a set comprehension",
1035            UnsupportedSyntaxErrorKind::UnpackingInComprehension(
1036                ComprehensionUnpackingKind::IterableInGenerator,
1037            ) => "Cannot use iterable unpacking in a generator expression",
1038            UnsupportedSyntaxErrorKind::UnpackingInComprehension(
1039                ComprehensionUnpackingKind::DictInDict,
1040            ) => "Cannot use dictionary unpacking in a dict comprehension",
1041            UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => {
1042                "Cannot use iterable unpacking in `for` statements"
1043            }
1044            UnsupportedSyntaxErrorKind::UnparenthesizedExceptionTypes => {
1045                "Multiple exception types must be parenthesized"
1046            }
1047            UnsupportedSyntaxErrorKind::TemplateStrings => "Cannot use t-strings",
1048        };
1049
1050        write!(
1051            f,
1052            "{kind} on Python {} (syntax was {changed})",
1053            self.target_version,
1054            changed = self.kind.changed_version(),
1055        )
1056    }
1057}
1058
1059#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
1060pub enum RelaxedDecoratorError {
1061    CallExpression,
1062    Other(&'static str),
1063}
1064
1065/// Represents the kind of change in Python syntax between versions.
1066enum Change {
1067    Added(PythonVersion),
1068    Removed(PythonVersion),
1069}
1070
1071impl Display for Change {
1072    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1073        match self {
1074            Change::Added(version) => write!(f, "added in Python {version}"),
1075            Change::Removed(version) => write!(f, "removed in Python {version}"),
1076        }
1077    }
1078}
1079
1080impl UnsupportedSyntaxErrorKind {
1081    /// Returns the Python version when the syntax associated with this error was changed, and the
1082    /// type of [`Change`] (added or removed).
1083    const fn changed_version(self) -> Change {
1084        match self {
1085            UnsupportedSyntaxErrorKind::Match => Change::Added(PythonVersion::PY310),
1086            UnsupportedSyntaxErrorKind::Walrus => Change::Added(PythonVersion::PY38),
1087            UnsupportedSyntaxErrorKind::ExceptStar => Change::Added(PythonVersion::PY311),
1088            UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(_) => {
1089                Change::Added(PythonVersion::PY39)
1090            }
1091            UnsupportedSyntaxErrorKind::StarTuple(_) => Change::Added(PythonVersion::PY38),
1092            UnsupportedSyntaxErrorKind::RelaxedDecorator { .. } => {
1093                Change::Added(PythonVersion::PY39)
1094            }
1095            UnsupportedSyntaxErrorKind::PositionalOnlyParameter => {
1096                Change::Added(PythonVersion::PY38)
1097            }
1098            UnsupportedSyntaxErrorKind::ParenthesizedKeywordArgumentName => {
1099                Change::Removed(PythonVersion::PY38)
1100            }
1101            UnsupportedSyntaxErrorKind::TypeParameterList => Change::Added(PythonVersion::PY312),
1102            UnsupportedSyntaxErrorKind::LazyImportStatement => Change::Added(PythonVersion::PY315),
1103            UnsupportedSyntaxErrorKind::TypeAliasStatement => Change::Added(PythonVersion::PY312),
1104            UnsupportedSyntaxErrorKind::TypeParamDefault => Change::Added(PythonVersion::PY313),
1105            UnsupportedSyntaxErrorKind::Pep701FString(_) => Change::Added(PythonVersion::PY312),
1106            UnsupportedSyntaxErrorKind::ParenthesizedContextManager => {
1107                Change::Added(PythonVersion::PY39)
1108            }
1109            UnsupportedSyntaxErrorKind::StarExpressionInIndex => {
1110                Change::Added(PythonVersion::PY311)
1111            }
1112            UnsupportedSyntaxErrorKind::StarAnnotation => Change::Added(PythonVersion::PY311),
1113            UnsupportedSyntaxErrorKind::UnpackingInComprehension(_) => {
1114                Change::Added(PythonVersion::PY315)
1115            }
1116            UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => {
1117                Change::Added(PythonVersion::PY39)
1118            }
1119            UnsupportedSyntaxErrorKind::UnparenthesizedExceptionTypes => {
1120                Change::Added(PythonVersion::PY314)
1121            }
1122            UnsupportedSyntaxErrorKind::TemplateStrings => Change::Added(PythonVersion::PY314),
1123        }
1124    }
1125
1126    /// Returns whether or not this kind of syntax is unsupported on `target_version`.
1127    pub(crate) fn is_unsupported(self, target_version: PythonVersion) -> bool {
1128        match self.changed_version() {
1129            Change::Added(version) => target_version < version,
1130            Change::Removed(version) => target_version >= version,
1131        }
1132    }
1133
1134    /// Returns `true` if this kind of syntax is supported on `target_version`.
1135    pub(crate) fn is_supported(self, target_version: PythonVersion) -> bool {
1136        !self.is_unsupported(target_version)
1137    }
1138}
1139
1140#[cfg(target_pointer_width = "64")]
1141mod sizes {
1142    use crate::error::{LexicalError, LexicalErrorType};
1143    use static_assertions::assert_eq_size;
1144
1145    assert_eq_size!(LexicalErrorType, [u8; 24]);
1146    assert_eq_size!(LexicalError, [u8; 32]);
1147}