rustpython_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 LazyImportStatement,
714 TypeAliasStatement,
715 TypeParamDefault,
716
717 /// Represents the use of a [PEP 701] f-string before Python 3.12.
718 ///
719 /// ## Examples
720 ///
721 /// As described in the PEP, each of these cases were invalid before Python 3.12:
722 ///
723 /// ```python
724 /// # nested quotes
725 /// f'Magic wand: { bag['wand'] }'
726 ///
727 /// # escape characters
728 /// f"{'\n'.join(a)}"
729 ///
730 /// # comments
731 /// f'''A complex trick: {
732 /// bag['bag'] # recursive bags!
733 /// }'''
734 ///
735 /// # arbitrary nesting
736 /// f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"
737 /// ```
738 ///
739 /// These restrictions were lifted in Python 3.12, meaning that all of these examples are now
740 /// valid.
741 ///
742 /// [PEP 701]: https://peps.python.org/pep-0701/
743 Pep701FString(FStringKind),
744
745 /// Represents the use of a parenthesized `with` item before Python 3.9.
746 ///
747 /// ## Examples
748 ///
749 /// As described in [BPO 12782], `with` uses like this were not allowed on Python 3.8:
750 ///
751 /// ```python
752 /// with (open("a_really_long_foo") as foo,
753 /// open("a_really_long_bar") as bar):
754 /// pass
755 /// ```
756 ///
757 /// because parentheses were not allowed within the `with` statement itself (see [this comment]
758 /// in particular). However, parenthesized expressions were still allowed, including the cases
759 /// below, so the issue can be pretty subtle and relates specifically to parenthesized items
760 /// with `as` bindings.
761 ///
762 /// ```python
763 /// with (foo, bar): ... # okay
764 /// with (
765 /// open('foo.txt')) as foo: ... # also okay
766 /// with (
767 /// foo,
768 /// bar,
769 /// baz,
770 /// ): ... # also okay, just a tuple
771 /// with (
772 /// foo,
773 /// bar,
774 /// baz,
775 /// ) as tup: ... # also okay, binding the tuple
776 /// ```
777 ///
778 /// This restriction was lifted in 3.9 but formally included in the [release notes] for 3.10.
779 ///
780 /// [BPO 12782]: https://github.com/python/cpython/issues/56991
781 /// [this comment]: https://github.com/python/cpython/issues/56991#issuecomment-1093555141
782 /// [release notes]: https://docs.python.org/3/whatsnew/3.10.html#summary-release-highlights
783 ParenthesizedContextManager,
784
785 /// Represents the use of a [PEP 646] star expression in an index.
786 ///
787 /// ## Examples
788 ///
789 /// Before Python 3.11, star expressions were not allowed in index/subscript operations (within
790 /// square brackets). This restriction was lifted in [PEP 646] to allow for star-unpacking of
791 /// `typing.TypeVarTuple`s, also added in Python 3.11. As such, this is the primary motivating
792 /// example from the PEP:
793 ///
794 /// ```python
795 /// from typing import TypeVar, TypeVarTuple
796 ///
797 /// DType = TypeVar('DType')
798 /// Shape = TypeVarTuple('Shape')
799 ///
800 /// class Array(Generic[DType, *Shape]): ...
801 /// ```
802 ///
803 /// But it applies to simple indexing as well:
804 ///
805 /// ```python
806 /// vector[*x]
807 /// array[a, *b]
808 /// ```
809 ///
810 /// [PEP 646]: https://peps.python.org/pep-0646/#change-1-star-expressions-in-indexes
811 StarExpressionInIndex,
812
813 /// Represents the use of a [PEP 646] star annotations in a function definition.
814 ///
815 /// ## Examples
816 ///
817 /// Before Python 3.11, star annotations were not allowed in function definitions. This
818 /// restriction was lifted in [PEP 646] to allow type annotations for `typing.TypeVarTuple`,
819 /// also added in Python 3.11:
820 ///
821 /// ```python
822 /// from typing import TypeVarTuple
823 ///
824 /// Ts = TypeVarTuple('Ts')
825 ///
826 /// def foo(*args: *Ts): ...
827 /// ```
828 ///
829 /// Unlike [`UnsupportedSyntaxErrorKind::StarExpressionInIndex`], this does not include any
830 /// other annotation positions:
831 ///
832 /// ```python
833 /// x: *Ts # Syntax error
834 /// def foo(x: *Ts): ... # Syntax error
835 /// ```
836 ///
837 /// [PEP 646]: https://peps.python.org/pep-0646/#change-2-args-as-a-typevartuple
838 StarAnnotation,
839
840 /// Represents the use of iterable unpacking inside a list comprehension
841 /// before Python 3.15.
842 ///
843 /// ## Examples
844 ///
845 /// Before Python 3.15, list comprehensions could not use iterable
846 /// unpacking in their element expression:
847 ///
848 /// ```python
849 /// [*x for x in y] # SyntaxError
850 /// ```
851 ///
852 /// Starting with Python 3.15, [PEP 798] allows iterable unpacking within
853 /// list comprehensions:
854 ///
855 /// ```python
856 /// [*x for x in y]
857 /// ```
858 ///
859 /// [PEP 798]: https://peps.python.org/pep-0798/
860 IterableUnpackingInListComprehension,
861
862 /// Represents the use of tuple unpacking in a `for` statement iterator clause before Python
863 /// 3.9.
864 ///
865 /// ## Examples
866 ///
867 /// Like [`UnsupportedSyntaxErrorKind::StarTuple`] in `return` and `yield` statements, prior to
868 /// Python 3.9, tuple unpacking in the iterator clause of a `for` statement required
869 /// parentheses:
870 ///
871 /// ```python
872 /// # valid on Python 3.8 and earlier
873 /// for i in (*a, *b): ...
874 /// ```
875 ///
876 /// Omitting the parentheses was invalid:
877 ///
878 /// ```python
879 /// for i in *a, *b: ... # SyntaxError
880 /// ```
881 ///
882 /// This was changed as part of the [PEG parser rewrite] included in Python 3.9 but not
883 /// documented directly until the [Python 3.11 release].
884 ///
885 /// [PEG parser rewrite]: https://peps.python.org/pep-0617/
886 /// [Python 3.11 release]: https://docs.python.org/3/whatsnew/3.11.html#other-language-changes
887 UnparenthesizedUnpackInFor,
888 /// Represents the use of multiple exception names in an except clause without an `as` binding, before Python 3.14.
889 ///
890 /// ## Examples
891 /// Before Python 3.14, catching multiple exceptions required
892 /// parentheses like so:
893 ///
894 /// ```python
895 /// try:
896 /// ...
897 /// except (ExceptionA, ExceptionB, ExceptionC):
898 /// ...
899 /// ```
900 ///
901 /// Starting with Python 3.14, thanks to [PEP 758], it was permitted
902 /// to omit the parentheses:
903 ///
904 /// ```python
905 /// try:
906 /// ...
907 /// except ExceptionA, ExceptionB, ExceptionC:
908 /// ...
909 /// ```
910 ///
911 /// However, parentheses are still required in the presence of an `as`:
912 ///
913 /// ```python
914 /// try:
915 /// ...
916 /// except (ExceptionA, ExceptionB, ExceptionC) as e:
917 /// ...
918 /// ```
919 ///
920 ///
921 /// [PEP 758]: https://peps.python.org/pep-0758/
922 UnparenthesizedExceptionTypes,
923 /// Represents the use of a template string (t-string)
924 /// literal prior to the implementation of [PEP 750]
925 /// in Python 3.14.
926 ///
927 /// [PEP 750]: https://peps.python.org/pep-0750/
928 TemplateStrings,
929}
930
931impl Display for UnsupportedSyntaxError {
932 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
933 let kind = match self.kind {
934 UnsupportedSyntaxErrorKind::Match => "Cannot use `match` statement",
935 UnsupportedSyntaxErrorKind::Walrus => "Cannot use named assignment expression (`:=`)",
936 UnsupportedSyntaxErrorKind::ExceptStar => "Cannot use `except*`",
937 UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
938 UnparenthesizedNamedExprKind::SequenceIndex,
939 ) => "Cannot use unparenthesized assignment expression in a sequence index",
940 UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
941 UnparenthesizedNamedExprKind::SetLiteral,
942 ) => "Cannot use unparenthesized assignment expression as an element in a set literal",
943 UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(
944 UnparenthesizedNamedExprKind::SetComprehension,
945 ) => {
946 "Cannot use unparenthesized assignment expression as an element in a set comprehension"
947 }
948 UnsupportedSyntaxErrorKind::ParenthesizedKeywordArgumentName => {
949 "Cannot use parenthesized keyword argument name"
950 }
951 UnsupportedSyntaxErrorKind::StarTuple(StarTupleKind::Return) => {
952 "Cannot use iterable unpacking in return statements"
953 }
954 UnsupportedSyntaxErrorKind::StarTuple(StarTupleKind::Yield) => {
955 "Cannot use iterable unpacking in yield expressions"
956 }
957 UnsupportedSyntaxErrorKind::RelaxedDecorator(relaxed_decorator_error) => {
958 return match relaxed_decorator_error {
959 RelaxedDecoratorError::CallExpression => {
960 write!(
961 f,
962 "Cannot use a call expression in a decorator on Python {} \
963 unless it is the top-level expression or it occurs \
964 in the argument list of a top-level call expression \
965 (relaxed decorator syntax was {changed})",
966 self.target_version,
967 changed = self.kind.changed_version(),
968 )
969 }
970 RelaxedDecoratorError::Other(description) => write!(
971 f,
972 "Cannot use {description} outside function call arguments in a decorator on Python {} \
973 (syntax was {changed})",
974 self.target_version,
975 changed = self.kind.changed_version(),
976 ),
977 };
978 }
979 UnsupportedSyntaxErrorKind::PositionalOnlyParameter => {
980 "Cannot use positional-only parameter separator"
981 }
982 UnsupportedSyntaxErrorKind::TypeParameterList => "Cannot use type parameter lists",
983 UnsupportedSyntaxErrorKind::LazyImportStatement => "Cannot use `lazy` import statement",
984 UnsupportedSyntaxErrorKind::TypeAliasStatement => "Cannot use `type` alias statement",
985 UnsupportedSyntaxErrorKind::TypeParamDefault => {
986 "Cannot set default type for a type parameter"
987 }
988 UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::Backslash) => {
989 "Cannot use an escape sequence (backslash) in f-strings"
990 }
991 UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::Comment) => {
992 "Cannot use comments in f-strings"
993 }
994 UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::NestedQuote) => {
995 "Cannot reuse outer quote character in f-strings"
996 }
997 UnsupportedSyntaxErrorKind::ParenthesizedContextManager => {
998 "Cannot use parentheses within a `with` statement"
999 }
1000 UnsupportedSyntaxErrorKind::StarExpressionInIndex => {
1001 "Cannot use star expression in index"
1002 }
1003 UnsupportedSyntaxErrorKind::StarAnnotation => "Cannot use star annotation",
1004 UnsupportedSyntaxErrorKind::IterableUnpackingInListComprehension => {
1005 "Cannot use iterable unpacking in a list comprehension"
1006 }
1007 UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => {
1008 "Cannot use iterable unpacking in `for` statements"
1009 }
1010 UnsupportedSyntaxErrorKind::UnparenthesizedExceptionTypes => {
1011 "Multiple exception types must be parenthesized"
1012 }
1013 UnsupportedSyntaxErrorKind::TemplateStrings => "Cannot use t-strings",
1014 };
1015
1016 write!(
1017 f,
1018 "{kind} on Python {} (syntax was {changed})",
1019 self.target_version,
1020 changed = self.kind.changed_version(),
1021 )
1022 }
1023}
1024
1025#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
1026pub enum RelaxedDecoratorError {
1027 CallExpression,
1028 Other(&'static str),
1029}
1030
1031/// Represents the kind of change in Python syntax between versions.
1032enum Change {
1033 Added(PythonVersion),
1034 Removed(PythonVersion),
1035}
1036
1037impl Display for Change {
1038 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1039 match self {
1040 Change::Added(version) => write!(f, "added in Python {version}"),
1041 Change::Removed(version) => write!(f, "removed in Python {version}"),
1042 }
1043 }
1044}
1045
1046impl UnsupportedSyntaxErrorKind {
1047 /// Returns the Python version when the syntax associated with this error was changed, and the
1048 /// type of [`Change`] (added or removed).
1049 const fn changed_version(self) -> Change {
1050 match self {
1051 UnsupportedSyntaxErrorKind::Match => Change::Added(PythonVersion::PY310),
1052 UnsupportedSyntaxErrorKind::Walrus => Change::Added(PythonVersion::PY38),
1053 UnsupportedSyntaxErrorKind::ExceptStar => Change::Added(PythonVersion::PY311),
1054 UnsupportedSyntaxErrorKind::UnparenthesizedNamedExpr(_) => {
1055 Change::Added(PythonVersion::PY39)
1056 }
1057 UnsupportedSyntaxErrorKind::StarTuple(_) => Change::Added(PythonVersion::PY38),
1058 UnsupportedSyntaxErrorKind::RelaxedDecorator { .. } => {
1059 Change::Added(PythonVersion::PY39)
1060 }
1061 UnsupportedSyntaxErrorKind::PositionalOnlyParameter => {
1062 Change::Added(PythonVersion::PY38)
1063 }
1064 UnsupportedSyntaxErrorKind::ParenthesizedKeywordArgumentName => {
1065 Change::Removed(PythonVersion::PY38)
1066 }
1067 UnsupportedSyntaxErrorKind::TypeParameterList => Change::Added(PythonVersion::PY312),
1068 UnsupportedSyntaxErrorKind::LazyImportStatement => Change::Added(PythonVersion::PY315),
1069 UnsupportedSyntaxErrorKind::TypeAliasStatement => Change::Added(PythonVersion::PY312),
1070 UnsupportedSyntaxErrorKind::TypeParamDefault => Change::Added(PythonVersion::PY313),
1071 UnsupportedSyntaxErrorKind::Pep701FString(_) => Change::Added(PythonVersion::PY312),
1072 UnsupportedSyntaxErrorKind::ParenthesizedContextManager => {
1073 Change::Added(PythonVersion::PY39)
1074 }
1075 UnsupportedSyntaxErrorKind::StarExpressionInIndex => {
1076 Change::Added(PythonVersion::PY311)
1077 }
1078 UnsupportedSyntaxErrorKind::StarAnnotation => Change::Added(PythonVersion::PY311),
1079 UnsupportedSyntaxErrorKind::IterableUnpackingInListComprehension => {
1080 Change::Added(PythonVersion::PY315)
1081 }
1082 UnsupportedSyntaxErrorKind::UnparenthesizedUnpackInFor => {
1083 Change::Added(PythonVersion::PY39)
1084 }
1085 UnsupportedSyntaxErrorKind::UnparenthesizedExceptionTypes => {
1086 Change::Added(PythonVersion::PY314)
1087 }
1088 UnsupportedSyntaxErrorKind::TemplateStrings => Change::Added(PythonVersion::PY314),
1089 }
1090 }
1091
1092 /// Returns whether or not this kind of syntax is unsupported on `target_version`.
1093 pub(crate) fn is_unsupported(self, target_version: PythonVersion) -> bool {
1094 match self.changed_version() {
1095 Change::Added(version) => target_version < version,
1096 Change::Removed(version) => target_version >= version,
1097 }
1098 }
1099
1100 /// Returns `true` if this kind of syntax is supported on `target_version`.
1101 pub(crate) fn is_supported(self, target_version: PythonVersion) -> bool {
1102 !self.is_unsupported(target_version)
1103 }
1104}
1105
1106#[cfg(target_pointer_width = "64")]
1107mod sizes {
1108 use crate::error::{LexicalError, LexicalErrorType};
1109 use static_assertions::assert_eq_size;
1110
1111 assert_eq_size!(LexicalErrorType, [u8; 24]);
1112 assert_eq_size!(LexicalError, [u8; 32]);
1113}