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}