diff --git a/Lib/test/test_metaclass.py b/Lib/test/test_metaclass.py index 1d3d6d13296..dfa6c633a63 100644 --- a/Lib/test/test_metaclass.py +++ b/Lib/test/test_metaclass.py @@ -128,7 +128,7 @@ Check for duplicate keywords. - >>> class C(metaclass=type, metaclass=type): pass # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE + >>> class C(metaclass=type, metaclass=type): pass ... Traceback (most recent call last): [...] diff --git a/Lib/test/test_named_expressions.py b/Lib/test/test_named_expressions.py index adf774f102f..617107e108e 100644 --- a/Lib/test/test_named_expressions.py +++ b/Lib/test/test_named_expressions.py @@ -63,7 +63,6 @@ def test_named_expression_invalid_10(self): with self.assertRaisesRegex(SyntaxError, "invalid syntax"): exec(code, {}, {}) - @unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message def test_named_expression_invalid_11(self): code = """spam(a=1, b := 2)""" @@ -71,7 +70,6 @@ def test_named_expression_invalid_11(self): "positional argument follows keyword argument"): exec(code, {}, {}) - @unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message def test_named_expression_invalid_12(self): code = """spam(a=1, (b := 2))""" @@ -79,7 +77,6 @@ def test_named_expression_invalid_12(self): "positional argument follows keyword argument"): exec(code, {}, {}) - @unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message def test_named_expression_invalid_13(self): code = """spam(a=1, (b := 2))""" diff --git a/Lib/test/test_positional_only_arg.py b/Lib/test/test_positional_only_arg.py index 1817592ca25..eea0625012d 100644 --- a/Lib/test/test_positional_only_arg.py +++ b/Lib/test/test_positional_only_arg.py @@ -23,7 +23,6 @@ def assertRaisesSyntaxError(self, codestr, regex="invalid syntax"): with self.assertRaisesRegex(SyntaxError, regex): compile(codestr + "\n", "", "single") - @unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message def test_invalid_syntax_errors(self): check_syntax_error(self, "def f(a, b = 5, /, c): pass", "parameter without a default follows parameter with a default") check_syntax_error(self, "def f(a = 5, b, /, c): pass", "parameter without a default follows parameter with a default") @@ -46,7 +45,6 @@ def test_invalid_syntax_errors(self): check_syntax_error(self, "def f(a, /, c, /, d, *, e): pass") check_syntax_error(self, "def f(a, *, c, /, d, e): pass") - @unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message def test_invalid_syntax_errors_async(self): check_syntax_error(self, "async def f(a, b = 5, /, c): pass", "parameter without a default follows parameter with a default") check_syntax_error(self, "async def f(a = 5, b, /, c): pass", "parameter without a default follows parameter with a default") @@ -235,7 +233,6 @@ def test_lambdas(self): x = lambda a, b, /, : a + b self.assertEqual(x(1, 2), 3) - @unittest.expectedFailure # TODO: RUSTPYTHON; wrong error message def test_invalid_syntax_lambda(self): check_syntax_error(self, "lambda a, b = 5, /, c: None", "parameter without a default follows parameter with a default") check_syntax_error(self, "lambda a = 5, b, /, c: None", "parameter without a default follows parameter with a default") diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 7d5ffc7a3f7..825d711edf1 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -398,12 +398,12 @@ From ast_for_arguments(): ->>> def f(x, y=1, z): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> def f(x, y=1, z): ... pass Traceback (most recent call last): SyntaxError: parameter without a default follows parameter with a default ->>> def f(x, /, y=1, z): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> def f(x, /, y=1, z): ... pass Traceback (most recent call last): SyntaxError: parameter without a default follows parameter with a default @@ -423,47 +423,47 @@ Traceback (most recent call last): SyntaxError: invalid syntax ->>> def foo(/,a,b=,c): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> def foo(/,a,b=,c): ... pass Traceback (most recent call last): SyntaxError: at least one argument must precede / ->>> def foo(a,/,/,b,c): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> def foo(a,/,/,b,c): ... pass Traceback (most recent call last): SyntaxError: / may appear only once ->>> def foo(a,/,a1,/,b,c): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> def foo(a,/,a1,/,b,c): ... pass Traceback (most recent call last): SyntaxError: / may appear only once ->>> 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: / may appear only once ->>> def foo(a,/,a1=1,/,b,c): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> def foo(a,/,a1=1,/,b,c): ... pass Traceback (most recent call last): SyntaxError: / may appear only once ->>> def foo(a,*b,c,/,d,e): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> def foo(a,*b,c,/,d,e): ... pass Traceback (most recent call last): SyntaxError: / must be ahead of * ->>> def foo(a=1,*b,c=3,/,d,e): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> def foo(a=1,*b,c=3,/,d,e): ... pass Traceback (most recent call last): SyntaxError: / must be ahead of * ->>> def foo(a,*b=3,c): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> def foo(a,*b=3,c): ... pass Traceback (most recent call last): SyntaxError: var-positional argument cannot have default value ->>> def foo(a,*b: int=,c): # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> def foo(a,*b: int=,c): ... pass Traceback (most recent call last): SyntaxError: var-positional argument cannot have default value @@ -543,31 +543,31 @@ Traceback (most recent call last): SyntaxError: expected default value expression ->>> lambda /,a,b,c: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> lambda /,a,b,c: None Traceback (most recent call last): SyntaxError: at least one argument must precede / ->>> lambda a,/,/,b,c: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> lambda a,/,/,b,c: None Traceback (most recent call last): SyntaxError: / may appear only once ->>> lambda a,/,a1,/,b,c: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> lambda a,/,a1,/,b,c: None Traceback (most recent call last): SyntaxError: / may appear only once ->>> 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: / may appear only once ->>> lambda a,/,a1=1,/,b,c: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> lambda a,/,a1=1,/,b,c: None Traceback (most recent call last): SyntaxError: / may appear only once ->>> lambda a,*b,c,/,d,e: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> lambda a,*b,c,/,d,e: None Traceback (most recent call last): SyntaxError: / must be ahead of * ->>> lambda a=1,*b,c=3,/,d,e: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> lambda a=1,*b,c=3,/,d,e: None Traceback (most recent call last): SyntaxError: / must be ahead of * @@ -575,7 +575,7 @@ Traceback (most recent call last): SyntaxError: expected comma between / and * ->>> lambda a,*b=3,c: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> lambda a,*b=3,c: None Traceback (most recent call last): SyntaxError: var-positional argument cannot have default value @@ -627,11 +627,11 @@ Traceback (most recent call last): SyntaxError: expected default value expression ->>> lambda a,d=3,c: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> lambda a,d=3,c: None Traceback (most recent call last): SyntaxError: parameter without a default follows parameter with a default ->>> lambda a,/,d=3,c: None # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> lambda a,/,d=3,c: None Traceback (most recent call last): SyntaxError: parameter without a default follows parameter with a default @@ -661,25 +661,25 @@ >>> L = range(10) >>> f(x for x in L) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ->>> f(x for x in L, 1) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> f(x for x in L, 1) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized ->>> f(x for x in L, y=1) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> f(x for x in L, y=1) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized ->>> f(x for x in L, *[]) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> f(x for x in L, *[]) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized ->>> f(x for x in L, **{}) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> f(x for x in L, **{}) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized ->>> f(L, x for x in L) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> f(L, x for x in L) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized ->>> f(x for x in L, y for y in L) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> f(x for x in L, y for y in L) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized ->>> f(x for x in L,) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> f(x for x in L,) Traceback (most recent call last): SyntaxError: Generator expression must be parenthesized >>> f((x for x in L), 1) @@ -1027,7 +1027,7 @@ SyntaxError: no binding for nonlocal 'x' found From SF bug #1705365 - >>> nonlocal x # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE + >>> nonlocal x Traceback (most recent call last): ... SyntaxError: nonlocal declaration not allowed at module level @@ -1351,7 +1351,7 @@ Custom error messages for try blocks that are not followed by except/finally - >>> try: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE + >>> try: ... x = 34 ... Traceback (most recent call last): @@ -1705,14 +1705,14 @@ Check that an multiple exception types with missing parentheses raise a custom exception only when using 'as' - >>> try: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE + >>> try: ... pass ... except A, B, C as blech: ... pass Traceback (most recent call last): SyntaxError: multiple exception types must be parenthesized when using 'as' - >>> try: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE + >>> try: ... pass ... except A, B, C as blech: ... pass @@ -1722,14 +1722,14 @@ SyntaxError: multiple exception types must be parenthesized when using 'as' - >>> try: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE + >>> try: ... pass ... except* A, B, C as blech: ... pass Traceback (most recent call last): SyntaxError: multiple exception types must be parenthesized when using 'as' - >>> try: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE + >>> try: ... pass ... except* A, B, C as blech: ... pass @@ -1880,7 +1880,7 @@ Traceback (most recent call last): SyntaxError: invalid syntax. Did you mean 'in'? ->>> f(a=23, a=234) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> f(a=23, a=234) Traceback (most recent call last): ... SyntaxError: keyword argument repeated: a @@ -2001,11 +2001,11 @@ Traceback (most recent call last): SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='? ->>> from t import x, # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> from t import x, Traceback (most recent call last): SyntaxError: trailing comma not allowed without surrounding parentheses ->>> from t import x,y, # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> from t import x,y, Traceback (most recent call last): SyntaxError: trailing comma not allowed without surrounding parentheses @@ -2164,31 +2164,31 @@ # 'not' after operators: ->>> 3 + not 3 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> 3 + not 3 Traceback (most recent call last): SyntaxError: 'not' after an operator must be parenthesized ->>> 3 * not 3 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> 3 * not 3 Traceback (most recent call last): SyntaxError: 'not' after an operator must be parenthesized ->>> + not 3 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> + not 3 Traceback (most recent call last): SyntaxError: 'not' after an operator must be parenthesized ->>> - not 3 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> - not 3 Traceback (most recent call last): SyntaxError: 'not' after an operator must be parenthesized ->>> ~ not 3 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> ~ not 3 Traceback (most recent call last): SyntaxError: 'not' after an operator must be parenthesized ->>> 3 + - not 3 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> 3 + - not 3 Traceback (most recent call last): SyntaxError: 'not' after an operator must be parenthesized ->>> 3 + not -1 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE +>>> 3 + not -1 Traceback (most recent call last): SyntaxError: 'not' after an operator must be parenthesized @@ -2660,7 +2660,7 @@ def f(x: *b) ... SyntaxError: yield expression cannot be used within the definition of a generic - >>> f(**x, *y) # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE + >>> f(**x, *y) Traceback (most recent call last): SyntaxError: iterable argument unpacking follows keyword argument unpacking @@ -2936,24 +2936,20 @@ def test_bad_outdent(self): "unindent does not match .* level", subclass=IndentationError) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_kwargs_last(self): self._check_error("int(base=10, '2')", "positional argument follows keyword argument") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_kwargs_last2(self): self._check_error("int(**{'base': 10}, '2')", "positional argument follows " "keyword argument unpacking") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_kwargs_last3(self): self._check_error("int(**{'base': 10}, *['2'])", "iterable argument unpacking follows " "keyword argument unpacking") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_generator_in_function_call(self): self._check_error("foo(x, y for y in range(3) for z in range(2) if z , p)", "Generator expression must be parenthesized", @@ -3126,7 +3122,6 @@ def test_invalid_line_continuation_error_position(self): "unexpected character after line continuation character", lineno=3, offset=4) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_line_continuation_left_recursive(self): # Check bpo-42218: SyntaxErrors following left-recursive rules # (t_primary_raw in this case) need to be tested explicitly @@ -3198,7 +3193,6 @@ def case(x): """ compile(code, "", "exec") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_multiline_compiler_error_points_to_the_end(self): self._check_error( "call(\na=1,\na=1\n)", diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index 60c8d2734a4..43e7e4b356a 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -2941,7 +2941,7 @@ impl SymbolTableBuilder { match role { SymbolUsage::Nonlocal if scope_depth < 2 => { return Err(SymbolTableError { - error: format!("cannot define nonlocal '{name}' at top level."), + error: "nonlocal declaration not allowed at module level".into(), location, }); } diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 66aae450d94..24e54a862c2 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -1,3 +1,11 @@ +use ruff_python_parser::{InterpolatedStringErrorType, LexicalErrorType, ParseErrorType}; + +use rustpython_common::wtf8::Wtf8Buf; +use rustpython_compiler_core::SourceLocation; + +#[cfg(feature = "parser")] +use rustpython_compiler::{CompileError, ParseError}; + use crate::{ AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, builtins::{ @@ -13,8 +21,6 @@ use crate::{ scope::Scope, vm::VirtualMachine, }; -use rustpython_common::wtf8::Wtf8Buf; -use rustpython_compiler_core::SourceLocation; macro_rules! define_exception_fn { ( @@ -25,14 +31,194 @@ macro_rules! define_exception_fn { stringify!($python_repr), " object.\nUseful for raising errors from python functions implemented in rust." )] - pub fn $fn_name(&self, msg: impl Into) -> PyBaseExceptionRef - { + pub fn $fn_name(&self, msg: impl Into) -> $crate::builtins::PyBaseExceptionRef { let err = self.ctx.exceptions.$attr.to_owned(); self.new_exception_msg(err, msg.into()) } }; } +#[derive(Clone, Debug)] +struct SyntaxErrorInfo { + msg: String, + narrow_caret: bool, +} + +impl SyntaxErrorInfo { + #[must_use] + const fn new(msg: String, narrow_caret: bool) -> Self { + Self { msg, narrow_caret } + } + + fn with_msg(&mut self, msg: &str) { + self.msg = msg.into(); + } + + #[cfg(feature = "parser")] + const fn with_narrow_caret(&mut self, narrow_caret: bool) { + self.narrow_caret = narrow_caret; + } + + #[cfg(feature = "parser")] + fn analyze_compile_error(&mut self, compile_error: &CompileError) { + let CompileError::Parse(ParseError { error, .. }) = compile_error else { + return; + }; + + let msg = match error { + ParseErrorType::FStringError(InterpolatedStringErrorType::UnterminatedString) + | ParseErrorType::Lexical(LexicalErrorType::FStringError( + InterpolatedStringErrorType::UnterminatedString, + )) => "unterminated f-string literal".into(), + + ParseErrorType::FStringError( + InterpolatedStringErrorType::UnterminatedTripleQuotedString, + ) + | ParseErrorType::Lexical(LexicalErrorType::FStringError( + InterpolatedStringErrorType::UnterminatedTripleQuotedString, + )) => "unterminated triple-quoted f-string literal".into(), + + ParseErrorType::FStringError(_) + | ParseErrorType::Lexical(LexicalErrorType::FStringError(_)) => { + // Replace backticks with single quotes to match CPython's error messages + format!("invalid syntax: {}", self.msg.replace('`', "'")) + } + + ParseErrorType::UnexpectedExpressionToken => format!("invalid syntax: {}", self.msg), + + ParseErrorType::Lexical(LexicalErrorType::UnrecognizedToken { .. }) + | ParseErrorType::SimpleStatementsOnSameLine + | ParseErrorType::SimpleAndCompoundStatementOnSameLine + | ParseErrorType::ExpectedToken { .. } + | ParseErrorType::ExpectedExpression => "invalid syntax".into(), + + ParseErrorType::InvalidStarredExpressionUsage => { + self.with_narrow_caret(true); + "invalid syntax".into() + } + + ParseErrorType::InvalidDeleteTarget => "invalid syntax".into(), + + ParseErrorType::Lexical(LexicalErrorType::LineContinuationError) => { + "unexpected character after line continuation character".into() + } + + ParseErrorType::Lexical(LexicalErrorType::UnclosedStringError) => { + "unterminated string literal".into() + } + + ParseErrorType::EmptyTypeParams => "Type parameter list cannot be empty".into(), + + ParseErrorType::InvalidStarPatternUsage => { + self.with_narrow_caret(true); + "cannot use starred expression here".into() + } + + ParseErrorType::ExpectedKeywordParam => "named arguments must follow bare *".into(), + + ParseErrorType::EmptyImportNames => "Expected one or more names after 'import'".into(), + + ParseErrorType::DuplicateKeywordArgumentError(arg_name) => { + format!("keyword argument repeated: {arg_name}") + } + + ParseErrorType::UnparenthesizedGeneratorExpression => { + "Generator expression must be parenthesized".into() + } + + ParseErrorType::NonDefaultParamAfterDefaultParam => { + "parameter without a default follows parameter with a default".into() + } + + ParseErrorType::VarParameterWithDefault => { + "var-positional argument cannot have default value".into() + } + + ParseErrorType::PositionalAfterKeywordArgument => { + "positional argument follows keyword argument".into() + } + + ParseErrorType::PositionalAfterKeywordUnpacking => { + "positional argument follows keyword argument unpacking".into() + } + + ParseErrorType::InvalidArgumentUnpackingOrder => { + "iterable argument unpacking follows keyword argument unpacking".into() + } + + ParseErrorType::OtherError(s) + if s.eq_ignore_ascii_case( + "bytes literal cannot be mixed with non-bytes literals", + ) => + { + "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") => + { + "positional patterns follow keyword patterns".into() + } + + ParseErrorType::OtherError(s) + if s.eq_ignore_ascii_case("boolean 'not' expression cannot be used here") => + { + "'not' after an operator must be parenthesized".into() + } + + ParseErrorType::OtherError(s) + if s.eq_ignore_ascii_case("trailing comma not allowed") => + { + "trailing comma not allowed without surrounding parentheses".into() + } + + ParseErrorType::OtherError(s) + if s.eq_ignore_ascii_case( + "multiple exception types must be parenthesized when using `as`", + ) => + { + "multiple exception types must be parenthesized when using 'as'".into() + } + + ParseErrorType::OtherError(s) + if s.eq_ignore_ascii_case( + "position-only parameter separator not allowed as first parameter", + ) => + { + "at least one argument must precede /".into() + } + + ParseErrorType::OtherError(s) + if s.eq_ignore_ascii_case("only one '/' separator allowed") => + { + "/ may appear only once".into() + } + + ParseErrorType::OtherError(s) + if s.eq_ignore_ascii_case("'/' parameter must appear before '*' parameter") => + { + "/ must be ahead of *".into() + } + + ParseErrorType::OtherError(s) + if s.eq_ignore_ascii_case("Expected `except` or `finally` after `try` block") => + { + "expected 'except' or 'finally' block".into() + } + + _ => return, + }; + + self.with_msg(&msg); + } +} + /// Collection of object creation helpers impl VirtualMachine { /// Create a new python object @@ -492,169 +678,31 @@ impl VirtualMachine { Some(line + "\n") } - let statement = if let Some(source) = source { - get_statement(source, error.location()) - } else { - None - }; + let statement = source.and_then(|src| get_statement(src, error.location())); let mut msg = error.to_string(); if let Some(msg) = msg.get_mut(..1) { msg.make_ascii_lowercase(); } - let mut narrow_caret = false; - match error { - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: - ruff_python_parser::ParseErrorType::FStringError( - ruff_python_parser::InterpolatedStringErrorType::UnterminatedString, - ) - | ruff_python_parser::ParseErrorType::Lexical( - ruff_python_parser::LexicalErrorType::FStringError( - ruff_python_parser::InterpolatedStringErrorType::UnterminatedString, - ), - ), - .. - }) => { - msg = "unterminated f-string literal".to_owned(); - } - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: - ruff_python_parser::ParseErrorType::FStringError( - ruff_python_parser::InterpolatedStringErrorType::UnterminatedTripleQuotedString, - ) - | ruff_python_parser::ParseErrorType::Lexical( - ruff_python_parser::LexicalErrorType::FStringError( - ruff_python_parser::InterpolatedStringErrorType::UnterminatedTripleQuotedString, - ), - ), - .. - }) => { - msg = "unterminated triple-quoted f-string literal".to_owned(); - } - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: - ruff_python_parser::ParseErrorType::FStringError(_) - | ruff_python_parser::ParseErrorType::Lexical( - ruff_python_parser::LexicalErrorType::FStringError(_), - ), - .. - }) => { - // Replace backticks with single quotes to match CPython's error messages - msg = msg.replace('`', "'"); - msg.insert_str(0, "invalid syntax: "); - } - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: ruff_python_parser::ParseErrorType::UnexpectedExpressionToken, - .. - }) => msg.insert_str(0, "invalid syntax: "), - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: - ruff_python_parser::ParseErrorType::Lexical( - ruff_python_parser::LexicalErrorType::UnrecognizedToken { .. }, - ) - | ruff_python_parser::ParseErrorType::SimpleStatementsOnSameLine - | ruff_python_parser::ParseErrorType::SimpleAndCompoundStatementOnSameLine - | ruff_python_parser::ParseErrorType::ExpectedToken { .. } - | ruff_python_parser::ParseErrorType::ExpectedExpression, - .. - }) => { - msg = "invalid syntax".to_owned(); - } - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: ruff_python_parser::ParseErrorType::InvalidStarredExpressionUsage, - .. - }) => { - msg = "invalid syntax".to_owned(); - narrow_caret = true; - } - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: ruff_python_parser::ParseErrorType::InvalidDeleteTarget, - .. - }) => { - msg = "invalid syntax".to_owned(); - } - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: - ruff_python_parser::ParseErrorType::Lexical( - ruff_python_parser::LexicalErrorType::LineContinuationError, - ), - .. - }) => { - msg = "unexpected character after line continuation".to_owned(); - } - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: - ruff_python_parser::ParseErrorType::Lexical( - ruff_python_parser::LexicalErrorType::UnclosedStringError, - ), - .. - }) => { - msg = "unterminated string".to_owned(); - } - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: ruff_python_parser::ParseErrorType::OtherError(s), - .. - }) if s.eq_ignore_ascii_case("bytes literal cannot be mixed with non-bytes literals") => { - msg = "cannot mix bytes and nonbytes literals".to_owned(); - } - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: ruff_python_parser::ParseErrorType::EmptyTypeParams, - .. - }) => { - msg = "Type parameter list cannot be empty".to_owned(); - }, - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: ruff_python_parser::ParseErrorType::InvalidStarPatternUsage, - .. - }) => { - msg = "cannot use starred expression here".to_owned(); - narrow_caret = true; - } - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: ruff_python_parser::ParseErrorType::ExpectedKeywordParam, - .. - }) => { - msg = "named arguments must follow bare *".to_owned(); - } - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: ruff_python_parser::ParseErrorType::EmptyImportNames, - .. - }) => { - msg = "Expected one or more names after 'import'".to_owned(); + + cfg_select! { + feature = "parser" => { + let mut syntax_error_info = SyntaxErrorInfo::new(msg, false); + syntax_error_info.analyze_compile_error(error); } - #[cfg(feature = "parser")] - crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { - error: ruff_python_parser::ParseErrorType::OtherError(s), - .. - }) => { - if s.starts_with("Expected an identifier, but found a keyword") { - msg = "invalid syntax".to_owned(); - } - else if s.eq_ignore_ascii_case("positional patterns cannot follow keyword patterns") { - msg = "positional patterns follow keyword patterns".to_owned(); - } + _ => { + let syntax_error_info = SyntaxErrorInfo::new(msg, false); } - _ => {} - } + }; + if syntax_error_type.is(self.ctx.exceptions.tab_error) { - msg = "inconsistent use of tabs and spaces in indentation".to_owned(); + syntax_error_info.with_msg("inconsistent use of tabs and spaces in indentation"); } + + let SyntaxErrorInfo { msg, narrow_caret } = syntax_error_info; + let syntax_error = self.new_exception_msg(syntax_error_type, msg.into()); + let (lineno, offset) = error.python_location(); let lineno = self.ctx.new_int(lineno); let offset = self.ctx.new_int(offset);