From 97f53b8a722713cb5834fb376829952fbbf4d16f Mon Sep 17 00:00:00 2001 From: mumallaeng Date: Mon, 17 Aug 2026 16:17:07 +0900 Subject: [PATCH 1/2] Collapse bare ExpectedExpression to 'invalid syntax'; fix '<>' diagnostic offset `ParseErrorType::ExpectedExpression` currently surfaces as the raw ruff parser message (e.g. "Expected an expression") to callers that only depend on `rustpython-compiler` (no `rustpython-vm`). `rustpython-vm`'s `vm_new.rs` already collapses this to CPython's generic "invalid syntax" for its own callers; mirror that same collapse inside `cpython_parse_diagnostic_override` so non-vm consumers get the same CPython-compatible message. A bare `<>` outside Barry-as-BDFL mode (`2 <> 3`) lexes as `Less` then an unexpected `Greater`, so the resulting `ExpectedExpression` location points at the `>` -- one character past where CPython's tokenizer (which treats `<>` as a single obsolete token) reports the error. Detect the `<` immediately preceding the location and shift the reported range back over it. Assisted-by: Claude Code:claude-sonnet-5 --- crates/compiler/src/lib.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index 7562e8939b9..062a705d4d7 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -362,9 +362,42 @@ fn cpython_parse_diagnostic_override( )); } + // `2 <> 3` outside Barry mode: ruff lexes `<` then an unexpected `>` and + // reports `ExpectedExpression` starting at the `>`. CPython's tokenizer + // treats `<>` as a single obsolete token and points at its start (the + // `<`) instead, so shift the reported location back over it. + source_error!(barry_flufl_obsolete_operator_error(error, source_text)); + + // CPython's PEG parser collapses a bare "expected an expression" failure + // into the generic "invalid syntax" message. rustpython-vm's `vm_new.rs` + // does this same collapse for its own callers; rustpython-compiler has no + // vm dependency, so mirror it here. + if matches!(&error.error, parser::ParseErrorType::ExpectedExpression) { + let (loc, end_loc) = adjusted_error_locations(source_file, error.location); + return Some(NormalizedParseDiagnostic::new( + parser::ParseErrorType::OtherError("invalid syntax".into()), + loc, + end_loc, + )); + } + None } +fn barry_flufl_obsolete_operator_error( + error: &parser::ParseError, + source: &str, +) -> Option<(String, usize, usize)> { + if !matches!(&error.error, parser::ParseErrorType::ExpectedExpression) { + return None; + } + let start = error.location.start().to_usize(); + if start == 0 || source.as_bytes().get(start - 1) != Some(&b'<') { + return None; + } + Some(("invalid syntax".to_string(), start - 1, start + 1)) +} + fn eof_parse_diagnostic( error: &parser::ParseError, source_file: &SourceFile, From 42900dc22b9972ebb6e749e6377163f4ea60eaf0 Mon Sep 17 00:00:00 2001 From: mumallaeng Date: Wed, 19 Aug 2026 19:19:31 +0900 Subject: [PATCH 2/2] Require an adjacent '>' before treating '<' as the obsolete <> operator CodeRabbit review on #8540: the previous check only looked at the byte before the ExpectedExpression location for '<', without confirming an adjacent '>' really follows it. For inputs like a trailing '<' at EOF this could misclassify an unrelated ExpectedExpression as the bare '<>' case and report a bogus range. Require both bytes are present before constructing the diagnostic. Also remove the now-stale `@unittest.expectedFailure # TODO: RUSTPYTHON` markers on test_guido_as_bdfl and test_barry_as_bdfl_relative_import, which pass with this fix (test_barry_as_bdfl and test_barry_as_bdfl_look_ma_with_no_compiler_flags still need real Barry-as-BDFL tokenizer support and stay marked). Assisted-by: Claude Code:claude-sonnet-5 --- Lib/test/test_flufl.py | 2 -- crates/compiler/src/lib.rs | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_flufl.py b/Lib/test/test_flufl.py index bd6267d45ae..62360d9f9e4 100644 --- a/Lib/test/test_flufl.py +++ b/Lib/test/test_flufl.py @@ -22,7 +22,6 @@ def test_barry_as_bdfl(self): # parser reports the start of the token self.assertEqual(cm.exception.offset, 3) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_guido_as_bdfl(self): code = '2 {0} 3' compile(code.format('!='), '', 'exec') @@ -50,7 +49,6 @@ def test_barry_as_bdfl_look_ma_with_no_compiler_flags(self): self.assertEqual(cm.exception.lineno, 1) self.assertEqual(cm.exception.offset, len(code) - 4) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_barry_as_bdfl_relative_import(self): code = "from .__future__ import barry_as_FLUFL;2 {0} 3" compile(code.format('!='), '', 'exec') diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index 062a705d4d7..fc9b67614b5 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -395,6 +395,9 @@ fn barry_flufl_obsolete_operator_error( if start == 0 || source.as_bytes().get(start - 1) != Some(&b'<') { return None; } + if source.as_bytes().get(start) != Some(&b'>') { + return None; + } Some(("invalid syntax".to_string(), start - 1, start + 1)) }