Skip to content

Commit cb7acec

Browse files
authored
compiler: end the missing-comma diagnostic past the whole second atom (RustPython#8594)
`missing_comma_expression_error` returned its span as `(index, next + 1)`, where `next` is the byte index at which the second atom starts. The `+ 1` is a fixed one-byte width, so the reported `end_offset` matched CPython only when that atom was a single ASCII character: (a b) end 5, CPython 5 (a bb) end 5, CPython 6 (a bbb) end 5, CPython 7 (a β) end 4, CPython 5 `adjacent_atom_end` already measures an atom -- identifiers including non-ASCII ones, string literals and numbers -- and is what the first atom is measured with three lines above. Use it for the second one too. Assisted-by: Claude
1 parent ed3111f commit cb7acec

1 file changed

Lines changed: 40 additions & 1 deletion

File tree

crates/compiler/src/lib.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4477,10 +4477,15 @@ fn missing_comma_expression_error(source: &str) -> Option<(String, usize, usize)
44774477
&& expression_atom_start(bytes, next)
44784478
&& !expression_continuation_keyword(bytes, next)
44794479
{
4480+
// The diagnostic covers both atoms, so it ends past
4481+
// the whole second one. `next + 1` ended past its
4482+
// first byte instead, which is only the same thing
4483+
// when that atom is one ASCII character.
4484+
let second_end = adjacent_atom_end(bytes, next).unwrap_or(next + 1);
44804485
return Some((
44814486
"invalid syntax. Perhaps you forgot a comma?".to_owned(),
44824487
index,
4483-
next + 1,
4488+
second_end,
44844489
));
44854490
}
44864491
index = atom_end;
@@ -5785,6 +5790,40 @@ mod tests {
57855790
assert_eq!(err.python_location(), (2, 3));
57865791
}
57875792

5793+
#[test]
5794+
fn missing_comma_diagnostic_spans_the_whole_second_atom() {
5795+
// `(start, end)` reported as one-based character columns, matching
5796+
// `SyntaxError.offset` / `.end_offset`.
5797+
let span = |source: &str| {
5798+
let err = compile(source, Mode::Eval, "<comma>", CompileOpts::default())
5799+
.expect_err("two adjacent atoms are a syntax error");
5800+
assert_eq!(
5801+
err.to_string(),
5802+
"invalid syntax. Perhaps you forgot a comma?"
5803+
);
5804+
(
5805+
err.python_location().1,
5806+
err.python_end_location().unwrap().1,
5807+
)
5808+
};
5809+
5810+
// A one-character second atom is the case that already worked.
5811+
assert_eq!(span("(a b)"), (2, 5));
5812+
// A longer one ends where it ends, not one byte in.
5813+
assert_eq!(span("(a bb)"), (2, 6));
5814+
assert_eq!(span("(a bbb)"), (2, 7));
5815+
assert_eq!(span("(1 22)"), (2, 6));
5816+
// A non-ASCII atom is one character but several bytes, so counting
5817+
// bytes here used to stop inside it and round back off the boundary.
5818+
assert_eq!(span("(a \u{3b2})"), (2, 5));
5819+
assert_eq!(span("(a \u{3b2}\u{3b2})"), (2, 6));
5820+
assert_eq!(span("(\u{3b1}\u{3b1} \u{3b2})"), (2, 6));
5821+
// The first atom's width was never the problem; pin it anyway.
5822+
assert_eq!(span("(\u{3b1} b)"), (2, 5));
5823+
// Other bracket kinds take the same path.
5824+
assert_eq!(span("[\u{3b1} \u{3b2}]"), (2, 5));
5825+
}
5826+
57885827
#[test]
57895828
fn obsolete_not_equal_diagnostic_spans_the_whole_operator() {
57905829
let err = compile("2 <> 3\n", Mode::Exec, "<obsolete>", CompileOpts::default())

0 commit comments

Comments
 (0)