Align more error messages with CPython 3.14.5 (#7933)

* Align more error messages with CPython 3.14.5

* Align patches for `test_eof.py`

* Unmark passing test

* Fix more
This commit is contained in:
Shahar Naveh
2026-05-20 16:20:13 +03:00
committed by GitHub
parent de06fc0923
commit ee006af13e
3 changed files with 116 additions and 72 deletions

11
Lib/test/test_eof.py vendored
View File

@@ -9,7 +9,6 @@ from test.support import warnings_helper
import unittest
class EOFTestCase(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_EOF_single_quote(self):
expect = "unterminated string literal (detected at line 1) (<string>, line 1)"
for quote in ("'", "\""):
@@ -19,7 +18,7 @@ class EOFTestCase(unittest.TestCase):
self.assertEqual(str(cm.exception), expect)
self.assertEqual(cm.exception.offset, 1)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_EOFS(self):
expect = ("unterminated triple-quoted string literal (detected at line 3) (<string>, line 1)")
with self.assertRaises(SyntaxError) as cm:
@@ -46,7 +45,7 @@ class EOFTestCase(unittest.TestCase):
self.assertEqual(cm.exception.text, "ä = '''thîs is ")
self.assertEqual(cm.exception.offset, 5)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
@force_not_colorized
def test_EOFS_with_file(self):
expect = ("(<string>, line 1)")
@@ -87,7 +86,7 @@ class EOFTestCase(unittest.TestCase):
' ^',
'SyntaxError: unterminated triple-quoted string literal (detected at line 4)'])
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
@warnings_helper.ignore_warnings(category=SyntaxWarning)
def test_eof_with_line_continuation(self):
expect = "unexpected EOF while parsing (<string>, line 1)"
@@ -95,7 +94,7 @@ class EOFTestCase(unittest.TestCase):
compile('"\\Xhh" \\', '<string>', 'exec')
self.assertEqual(str(cm.exception), expect)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_line_continuation_EOF(self):
"""A continuation at the end of input must be an error; bpo2180."""
expect = 'unexpected EOF while parsing (<string>, line 1)'
@@ -128,7 +127,7 @@ class EOFTestCase(unittest.TestCase):
exec('\\')
self.assertEqual(str(cm.exception), expect)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipIf(not sys.executable, "sys.executable required")
@force_not_colorized
def test_line_continuation_EOF_from_file_bpo2180(self):

View File

@@ -156,15 +156,15 @@ SyntaxError: cannot assign to expression
Traceback (most recent call last):
SyntaxError: cannot assign to conditional expression
>>> a = 42 if True # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> a = 42 if True
Traceback (most recent call last):
SyntaxError: expected 'else' after 'if' expression
>>> a = (42 if True) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> a = (42 if True)
Traceback (most recent call last):
SyntaxError: expected 'else' after 'if' expression
>>> a = [1, 42 if True, 4] # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> a = [1, 42 if True, 4]
Traceback (most recent call last):
SyntaxError: expected 'else' after 'if' expression
@@ -275,7 +275,7 @@ SyntaxError: cannot assign to function call
Traceback (most recent call last):
SyntaxError: cannot assign to function call
>>> with a as b # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> with a as b
Traceback (most recent call last):
SyntaxError: expected ':'
@@ -478,47 +478,47 @@ SyntaxError: var-keyword argument cannot have default value
Traceback (most recent call last):
SyntaxError: var-keyword argument cannot have default value
>>> def foo(a,*a, b, **c, d): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def foo(a,*a, b, **c, d):
... pass
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument
>>> def foo(a,*a, b, **c, d=4): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def foo(a,*a, b, **c, d=4):
... pass
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument
>>> def foo(a,*a, b, **c, *d): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def foo(a,*a, b, **c, *d):
... pass
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument
>>> def foo(a,*a, b, **c, **d): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def foo(a,*a, b, **c, **d):
... pass
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument
>>> def foo(a=1,/,**b,/,c): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def foo(a=1,/,**b,/,c):
... pass
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument
>>> def foo(*b,*d): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def foo(*b,*d):
... pass
Traceback (most recent call last):
SyntaxError: * argument may appear only once
>>> def foo(a,*b,c,*d,*e,c): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def foo(a,*b,c,*d,*e,c):
... pass
Traceback (most recent call last):
SyntaxError: * argument may appear only once
>>> def foo(a,b,/,c,*b,c,*d,*e,c): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def foo(a,b,/,c,*b,c,*d,*e,c):
... pass
Traceback (most recent call last):
SyntaxError: * argument may appear only once
>>> def foo(a,b,/,c,*b,c,*d,**e): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def foo(a,b,/,c,*b,c,*d,**e):
... pass
Traceback (most recent call last):
SyntaxError: * argument may appear only once
@@ -583,39 +583,39 @@ SyntaxError: var-positional argument cannot have default value
Traceback (most recent call last):
SyntaxError: var-keyword argument cannot have default value
>>> lambda a, *a, b, **c, d: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> lambda a, *a, b, **c, d: None
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument
>>> lambda a,*a, b, **c, d=4: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> lambda a,*a, b, **c, d=4: None
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument
>>> lambda a,*a, b, **c, *d: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> lambda a,*a, b, **c, *d: None
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument
>>> lambda a,*a, b, **c, **d: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> lambda a,*a, b, **c, **d: None
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument
>>> lambda a=1,/,**b,/,c: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> lambda a=1,/,**b,/,c: None
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument
>>> lambda *b,*d: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> lambda *b,*d: None
Traceback (most recent call last):
SyntaxError: * argument may appear only once
>>> lambda a,*b,c,*d,*e,c: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> lambda a,*b,c,*d,*e,c: None
Traceback (most recent call last):
SyntaxError: * argument may appear only once
>>> lambda a,b,/,c,*b,c,*d,*e,c: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> lambda a,b,/,c,*b,c,*d,*e,c: None
Traceback (most recent call last):
SyntaxError: * argument may appear only once
>>> lambda a,b,/,c,*b,c,*d,**e: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> lambda a,b,/,c,*b,c,*d,**e: None
Traceback (most recent call last):
SyntaxError: * argument may appear only once
@@ -1093,27 +1093,27 @@ leading to spurious errors.
Missing ':' before suites:
>>> def f() # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def f()
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> def f[T]() # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def f[T]()
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> class A # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> class A
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> class A[T] # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> class A[T]
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> class A[T]() # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> class A[T]()
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
@@ -1123,7 +1123,7 @@ Missing ':' before suites:
Traceback (most recent call last):
SyntaxError: invalid syntax
>>> if 1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> if 1
... pass
... elif 1:
... pass
@@ -1132,7 +1132,7 @@ Missing ':' before suites:
Traceback (most recent call last):
SyntaxError: expected ':'
>>> if 1: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> if 1:
... pass
... elif 1
... pass
@@ -1141,7 +1141,7 @@ Missing ':' before suites:
Traceback (most recent call last):
SyntaxError: expected ':'
>>> if 1: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> if 1:
... pass
... elif 1:
... pass
@@ -1150,7 +1150,7 @@ Missing ':' before suites:
Traceback (most recent call last):
SyntaxError: expected ':'
>>> for x in range(10) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> for x in range(10)
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
@@ -1160,47 +1160,47 @@ Missing ':' before suites:
Traceback (most recent call last):
SyntaxError: invalid syntax
>>> while True # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> while True
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> with blech as something # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> with blech as something
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> with blech # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> with blech
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> with blech, block as something # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> with blech, block as something
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> with blech, block as something, bluch # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> with blech, block as something, bluch
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> with (blech as something) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> with (blech as something)
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> with (blech) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> with (blech)
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> with (blech, block as something) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> with (blech, block as something)
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> with (blech, block as something, bluch) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> with (blech, block as something, bluch)
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
@@ -1210,19 +1210,19 @@ Missing ':' before suites:
Traceback (most recent call last):
SyntaxError: invalid syntax. Did you mean 'and'?
>>> try # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> try
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> try: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> try:
... pass
... except
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> match x # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> match x
... case list():
... pass
Traceback (most recent call last):
@@ -1234,13 +1234,13 @@ Missing ':' before suites:
Traceback (most recent call last):
SyntaxError: invalid syntax
>>> match x: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> match x:
... case list()
... pass
Traceback (most recent call last):
SyntaxError: expected ':'
>>> match x: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> match x:
... case [y] if y > 0
... pass
Traceback (most recent call last):
@@ -1287,27 +1287,27 @@ Missing ':' before suites:
Missing parens after function definition
>>> def f: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def f:
Traceback (most recent call last):
SyntaxError: expected '('
>>> async def f: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> async def f:
Traceback (most recent call last):
SyntaxError: expected '('
>>> def f -> int: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def f -> int:
Traceback (most recent call last):
SyntaxError: expected '('
>>> async def f -> int: # type: int # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> async def f -> int: # type: int
Traceback (most recent call last):
SyntaxError: expected '('
>>> async def f[T]: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> async def f[T]:
Traceback (most recent call last):
SyntaxError: expected '('
>>> def f[T] -> str: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> def f[T] -> str:
Traceback (most recent call last):
SyntaxError: expected '('
@@ -3092,7 +3092,6 @@ class A:
with self.subTest(f"out of range: {n=}"):
self._check_error(get_code(n), "too many statically nested blocks")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_barry_as_flufl_with_syntax_errors(self):
# The "barry_as_flufl" rule can produce some "bugs-at-a-distance" if
# is reading the wrong token in the presence of syntax errors later

View File

@@ -1,3 +1,6 @@
#[cfg(feature = "parser")]
use ruff_python_ast::token::TokenKind;
use ruff_python_parser::{InterpolatedStringErrorType, LexicalErrorType, ParseErrorType};
use rustpython_common::wtf8::Wtf8Buf;
@@ -59,9 +62,28 @@ impl SyntaxErrorInfo {
self.narrow_caret = narrow_caret;
}
#[cfg(feature = "parser")]
#[must_use]
const fn handle_expected_token(expected: &TokenKind, found: &TokenKind) -> &'static str {
match (*expected, *found) {
(TokenKind::Colon, TokenKind::Newline) => "expected ':'",
(TokenKind::Lpar, _) => "expected '('",
(TokenKind::Else, y) if !matches!(y, TokenKind::Colon) => {
"expected 'else' after 'if' expression"
}
_ => "invalid syntax",
}
}
#[cfg(feature = "parser")]
fn analyze_compile_error(&mut self, compile_error: &CompileError) {
let CompileError::Parse(ParseError { error, .. }) = compile_error else {
let CompileError::Parse(ParseError {
error, location, ..
}) = compile_error
else {
return;
};
@@ -86,11 +108,9 @@ impl SyntaxErrorInfo {
ParseErrorType::UnexpectedExpressionToken => format!("invalid syntax: {}", self.msg),
ParseErrorType::Lexical(LexicalErrorType::UnrecognizedToken { .. })
| ParseErrorType::SimpleStatementsOnSameLine
| ParseErrorType::SimpleAndCompoundStatementOnSameLine
| ParseErrorType::ExpectedToken { .. }
| ParseErrorType::ExpectedExpression => "invalid syntax".into(),
ParseErrorType::ExpectedToken { expected, found } => {
Self::handle_expected_token(expected, found).into()
}
ParseErrorType::InvalidStarredExpressionUsage => {
self.with_narrow_caret(true);
@@ -104,7 +124,10 @@ impl SyntaxErrorInfo {
}
ParseErrorType::Lexical(LexicalErrorType::UnclosedStringError) => {
"unterminated string literal".into()
format!(
"unterminated string literal (detected at line {})",
location.line
)
}
ParseErrorType::EmptyTypeParams => "Type parameter list cannot be empty".into(),
@@ -146,6 +169,21 @@ impl SyntaxErrorInfo {
"iterable argument unpacking follows keyword argument unpacking".into()
}
ParseErrorType::ParamAfterVarKeywordParam => {
"arguments cannot follow var-keyword argument".into()
}
ParseErrorType::Lexical(LexicalErrorType::UnrecognizedToken { .. })
| ParseErrorType::SimpleStatementsOnSameLine
| ParseErrorType::SimpleAndCompoundStatementOnSameLine
| ParseErrorType::ExpectedExpression => "invalid syntax".into(),
ParseErrorType::OtherError(s)
if s.starts_with("Expected an identifier, but found a keyword") =>
{
"invalid syntax".into()
}
ParseErrorType::OtherError(s)
if s.eq_ignore_ascii_case(
"bytes literal cannot be mixed with non-bytes literals",
@@ -154,12 +192,6 @@ impl SyntaxErrorInfo {
"cannot mix bytes and nonbytes literals".into()
}
ParseErrorType::OtherError(s)
if s.starts_with("Expected an identifier, but found a keyword") =>
{
"invalid syntax".into()
}
ParseErrorType::OtherError(s)
if s.eq_ignore_ascii_case("positional patterns cannot follow keyword patterns") =>
{
@@ -207,11 +239,25 @@ impl SyntaxErrorInfo {
}
ParseErrorType::OtherError(s)
if s.eq_ignore_ascii_case("Expected `except` or `finally` after `try` block") =>
if s.eq_ignore_ascii_case("expected `except` or `finally` after `try` block") =>
{
"expected 'except' or 'finally' block".into()
}
ParseErrorType::OtherError(s)
if s.eq_ignore_ascii_case("only one '*' parameter allowed") =>
{
"* argument may appear only once".into()
}
ParseErrorType::OtherError(s)
if s.eq_ignore_ascii_case(
r#"cannot have both 'except' and 'except*' on the same 'try'"#,
) =>
{
r#"cannot have both 'except' and 'except*' on the same 'try'"#.into()
}
_ => return,
};