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