From 4ec7e6184c928b46a36e4bd916d9f2cac36913c8 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 15:17:31 +0300 Subject: [PATCH 01/15] Cleanup --- crates/vm/src/vm/vm_new.rs | 234 ++++++++++++++++++------------------- 1 file changed, 111 insertions(+), 123 deletions(-) diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 63de652ac7a..626cd2cd0aa 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -1,3 +1,9 @@ +use ruff_python_parser::{InterpolatedStringErrorType, LexicalErrorType, ParseErrorType}; + +use rustpython_common::wtf8::Wtf8Buf; +use rustpython_compiler::{CompileError, ParseError}; +use rustpython_compiler_core::SourceLocation; + use crate::{ AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, builtins::{ @@ -13,8 +19,6 @@ use crate::{ scope::Scope, vm::VirtualMachine, }; -use rustpython_common::wtf8::Wtf8Buf; -use rustpython_compiler_core::SourceLocation; macro_rules! define_exception_fn { ( @@ -25,14 +29,102 @@ 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(); + } + + 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".into() + } + + ParseErrorType::Lexical(LexicalErrorType::UnclosedStringError) => { + "unterminated string".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() + } + + _ => return, + }; + + self.with_msg(&msg); + } +} + /// Collection of object creation helpers impl VirtualMachine { /// Create a new python object @@ -492,135 +584,31 @@ impl VirtualMachine { Some(line + "\n") } - let statement = if let Some(source) = source { - get_statement(source, error.location()) - } else { - None - }; + let statement = source.map(|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_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(); + _ => { + 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".into()); } + + 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); From fc0c75becf9050447a5864ef2d97d9d6fafc2c3d Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 15:26:03 +0300 Subject: [PATCH 02/15] fixes after merge --- crates/vm/src/vm/vm_new.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 626cd2cd0aa..9538a803600 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -52,6 +52,7 @@ impl SyntaxErrorInfo { self.msg = msg.into(); } + #[cfg(feature = "parser")] const fn with_narrow_caret(&mut self, narrow_caret: bool) { self.narrow_caret = narrow_caret; } @@ -104,6 +105,17 @@ impl SyntaxErrorInfo { "unterminated string".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::OtherError(s) if s.eq_ignore_ascii_case( "bytes literal cannot be mixed with non-bytes literals", @@ -118,6 +130,12 @@ impl SyntaxErrorInfo { "invalid syntax".into() } + ParseErrorType::OtherError(s) + if s.eq_ignore_ascii_case("positional patterns cannot follow keyword patterns") => + { + "positional patterns follow keyword patterns".into() + } + _ => return, }; From 95fb51f22760818905dec4fe73f45a97f81855bb Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 15:32:32 +0300 Subject: [PATCH 03/15] Fixed repeated keyword error message --- Lib/test/test_syntax.py | 3 +-- crates/vm/src/vm/vm_new.rs | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 7d5ffc7a3f7..730f477513a 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -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 @@ -3198,7 +3198,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/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 9538a803600..5ee173a5099 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -116,6 +116,10 @@ impl SyntaxErrorInfo { ParseErrorType::EmptyImportNames => "Expected one or more names after 'import'".into(), + ParseErrorType::DuplicateKeywordArgumentError(arg_name) => { + format!("keyword argument repeated: {arg_name}") + } + ParseErrorType::OtherError(s) if s.eq_ignore_ascii_case( "bytes literal cannot be mixed with non-bytes literals", From e3ab7bc8bd26bfcfb6e40cf873e278cdc3542ea5 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 15:53:26 +0300 Subject: [PATCH 04/15] fix `not` error message --- Lib/test/test_syntax.py | 14 +++++++------- crates/vm/src/vm/vm_new.rs | 11 ++++++++++- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 730f477513a..dd5cc9fa9f2 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -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 diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 5ee173a5099..7c8611e077a 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -140,7 +140,16 @@ impl SyntaxErrorInfo { "positional patterns follow keyword patterns".into() } - _ => return, + ParseErrorType::OtherError(s) + if s.eq_ignore_ascii_case("Boolean 'not' expression cannot be used here") => + { + "'not' after an operator must be parenthesized".into() + } + + _ => { + dbg!(error); + return; + } }; self.with_msg(&msg); From 97a56598006af0b9ccb41b0c5b0a1c7e5eb45a34 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 16:05:28 +0300 Subject: [PATCH 05/15] Trailing comma --- Lib/test/test_syntax.py | 4 ++-- crates/vm/src/vm/vm_new.rs | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index dd5cc9fa9f2..693c24de1c4 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -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 diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 7c8611e077a..e313d62002c 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -141,11 +141,17 @@ impl SyntaxErrorInfo { } ParseErrorType::OtherError(s) - if s.eq_ignore_ascii_case("Boolean 'not' expression cannot be used here") => + 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() + } + _ => { dbg!(error); return; From b0f24c4acbd7b1780856c867d84ce4eff92103c4 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 16:11:32 +0300 Subject: [PATCH 06/15] fix more --- Lib/test/test_syntax.py | 8 ++++---- crates/vm/src/vm/vm_new.rs | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 693c24de1c4..6e87328659f 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -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 diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index e313d62002c..576484e4fe3 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -152,6 +152,14 @@ impl SyntaxErrorInfo { "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() + } + _ => { dbg!(error); return; From eb83c139eb978aa13ea753954cb3eeb77354e5d2 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 16:36:09 +0300 Subject: [PATCH 07/15] Fix more --- Lib/test/test_syntax.py | 15 +++++++-------- crates/vm/src/vm/vm_new.rs | 4 ++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 6e87328659f..87d3b677203 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -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) @@ -2953,7 +2953,6 @@ def test_kwargs_last3(self): "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", diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 576484e4fe3..14ae3c46f1d 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -120,6 +120,10 @@ impl SyntaxErrorInfo { format!("keyword argument repeated: {arg_name}") } + ParseErrorType::UnparenthesizedGeneratorExpression => { + "Generator expression must be parenthesized".into() + } + ParseErrorType::OtherError(s) if s.eq_ignore_ascii_case( "bytes literal cannot be mixed with non-bytes literals", From a14e54b00e613079a0690321b06c601499013418 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 16:56:35 +0300 Subject: [PATCH 08/15] More --- Lib/test/test_syntax.py | 42 +++++++++++++++++++------------------- crates/vm/src/vm/vm_new.rs | 31 +++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 87d3b677203..681cf26bb3e 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 diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 14ae3c46f1d..c44ea67f1fe 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -124,6 +124,14 @@ impl SyntaxErrorInfo { "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::OtherError(s) if s.eq_ignore_ascii_case( "bytes literal cannot be mixed with non-bytes literals", @@ -164,10 +172,27 @@ impl SyntaxErrorInfo { "multiple exception types must be parenthesized when using 'as'".into() } - _ => { - dbg!(error); - return; + 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() + } + + _ => return, }; self.with_msg(&msg); From bfec825dfd29106879bfb8bd96571f03a235d48b Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 17:19:52 +0300 Subject: [PATCH 09/15] clippy --- crates/vm/src/vm/vm_new.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index c44ea67f1fe..ba94c28eecf 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -676,7 +676,7 @@ impl VirtualMachine { }; if syntax_error_type.is(self.ctx.exceptions.tab_error) { - syntax_error_info.with_msg("inconsistent use of tabs and spaces in indentation".into()); + syntax_error_info.with_msg("inconsistent use of tabs and spaces in indentation"); } let SyntaxErrorInfo { msg, narrow_caret } = syntax_error_info; From 91147a87426aa9fc3f462303127f78f4e072f4b0 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 18:41:45 +0300 Subject: [PATCH 10/15] unmark passing tests --- Lib/test/test_metaclass.py | 2 +- Lib/test/test_positional_only_arg.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) 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_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") From 666443d1a48b2ef775b522e41f003e03b69c67a9 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 18:48:42 +0300 Subject: [PATCH 11/15] Fix more --- Lib/test/test_syntax.py | 1 - crates/vm/src/vm/vm_new.rs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 681cf26bb3e..50ffbc631ef 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -3125,7 +3125,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 diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index ba94c28eecf..87ec396df33 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -98,11 +98,11 @@ impl SyntaxErrorInfo { ParseErrorType::InvalidDeleteTarget => "invalid syntax".into(), ParseErrorType::Lexical(LexicalErrorType::LineContinuationError) => { - "unexpected character after line continuation".into() + "unexpected character after line continuation character".into() } ParseErrorType::Lexical(LexicalErrorType::UnclosedStringError) => { - "unterminated string".into() + "unterminated string literal".into() } ParseErrorType::EmptyTypeParams => "Type parameter list cannot be empty".into(), From 42005e7214986988dfbff54daf5cf4edae1693bf Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 19:07:18 +0300 Subject: [PATCH 12/15] Fix more --- Lib/test/test_syntax.py | 9 +++------ crates/codegen/src/symboltable.rs | 2 +- crates/vm/src/vm/vm_new.rs | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 50ffbc631ef..825d711edf1 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -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): @@ -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,18 +2936,15 @@ 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 " 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 87ec396df33..ab00e8ef9bd 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -132,6 +132,18 @@ impl SyntaxErrorInfo { "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", @@ -192,6 +204,12 @@ impl SyntaxErrorInfo { "/ 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, }; From 0327d32967e2cfafe40ef50fd8de2e8c2f774f6d Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 19:09:31 +0300 Subject: [PATCH 13/15] cfg gate --- crates/vm/src/vm/vm_new.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index ab00e8ef9bd..8b0a8415363 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -1,9 +1,11 @@ use ruff_python_parser::{InterpolatedStringErrorType, LexicalErrorType, ParseErrorType}; use rustpython_common::wtf8::Wtf8Buf; -use rustpython_compiler::{CompileError, ParseError}; use rustpython_compiler_core::SourceLocation; +#[cfg(feature = "parser")] +use rustpython_compiler::{CompileError, ParseError}; + use crate::{ AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, builtins::{ From 73cb383c803c06c36aee71722cfb8387d50152e9 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 19:34:30 +0300 Subject: [PATCH 14/15] Unmark more passing tests --- Lib/test/test_named_expressions.py | 3 --- 1 file changed, 3 deletions(-) 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))""" From 6a94654f02e282ebfe71c97d7709db2725668f78 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 19 May 2026 21:44:25 +0300 Subject: [PATCH 15/15] Fix logic error --- crates/vm/src/vm/vm_new.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 8b0a8415363..24e54a862c2 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -678,7 +678,7 @@ impl VirtualMachine { Some(line + "\n") } - let statement = source.map(|src| get_statement(src, error.location())); + 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) {