Skip to content

Commit d8231f5

Browse files
committed
Detect assignment expressions before Python 3.8
This PR is the first in a series derived from #16308, each of which add support for detecting one version-related syntax error from #6591. This one should be the largest because it also includes a couple of additional changes: 1. the `syntax_errors!` macro, which makes adding more variants a bit easier 2. the `Parser::add_unsupported_syntax_error` method Otherwise I think the general structure will be the same for each syntax error: * Detecting the error in the parser * Inline parser tests for the new error * New ruff CLI tests for the new error Because of the second point here, this PR is currently stacked on #16357. As noted above, there are new inline parser tests, as well as new ruff CLI tests. Once #16379 is resolved, there should also be new mdtests for red-knot, but this PR does not currently include those.
1 parent 259189b commit d8231f5

File tree

7 files changed

+176
-3
lines changed

7 files changed

+176
-3
lines changed

crates/ruff/tests/lint.rs

+41
Original file line numberDiff line numberDiff line change
@@ -2628,6 +2628,47 @@ class A(Generic[T]):
26282628
);
26292629
}
26302630

2631+
#[test]
2632+
fn walrus_before_py38() {
2633+
// ok
2634+
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
2635+
.args(STDIN_BASE_OPTIONS)
2636+
.args(["--stdin-filename", "test.py"])
2637+
.arg("--target-version=py38")
2638+
.arg("-")
2639+
.pass_stdin(r#"if x := 1: pass"#),
2640+
@r"
2641+
success: false
2642+
exit_code: 1
2643+
----- stdout -----
2644+
test.py:1:10: E701 Multiple statements on one line (colon)
2645+
Found 1 error.
2646+
2647+
----- stderr -----
2648+
"
2649+
);
2650+
2651+
// not ok on 3.7 with preview
2652+
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
2653+
.args(STDIN_BASE_OPTIONS)
2654+
.args(["--stdin-filename", "test.py"])
2655+
.arg("--target-version=py37")
2656+
.arg("--preview")
2657+
.arg("-")
2658+
.pass_stdin(r#"if x := 1: pass"#),
2659+
@r"
2660+
success: false
2661+
exit_code: 1
2662+
----- stdout -----
2663+
test.py:1:4: SyntaxError: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
2664+
test.py:1:10: E701 Multiple statements on one line (colon)
2665+
Found 2 errors.
2666+
2667+
----- stderr -----
2668+
"
2669+
);
2670+
}
2671+
26312672
#[test]
26322673
fn match_before_py310() {
26332674
// ok on 3.10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# parse_options: { "target_version": "3.7" }
2+
if x := 1: ...
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# parse_options: { "target_version": "3.8" }
2+
if x := 1: ...

crates/ruff_python_parser/src/error.rs

+1
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ macro_rules! syntax_errors {
479479

480480
syntax_errors! {
481481
(MatchBeforePy310, PY310, "`match` statement"),
482+
(WalrusBeforePy38, PY38, "named assignment expression (`:=`)"),
482483
}
483484

484485
#[cfg(target_pointer_width = "64")]

crates/ruff_python_parser/src/parser/expression.rs

+17-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_hash::{FxBuildHasher, FxHashSet};
77
use ruff_python_ast::name::Name;
88
use ruff_python_ast::{
99
self as ast, BoolOp, CmpOp, ConversionFlag, Expr, ExprContext, FStringElement, FStringElements,
10-
IpyEscapeKind, Number, Operator, StringFlags, UnaryOp,
10+
IpyEscapeKind, Number, Operator, PythonVersion, StringFlags, UnaryOp,
1111
};
1212
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
1313

@@ -16,7 +16,7 @@ use crate::parser::{helpers, FunctionKind, Parser};
1616
use crate::string::{parse_fstring_literal_element, parse_string_literal, StringType};
1717
use crate::token::{TokenKind, TokenValue};
1818
use crate::token_set::TokenSet;
19-
use crate::{FStringErrorType, Mode, ParseErrorType};
19+
use crate::{FStringErrorType, Mode, ParseErrorType, UnsupportedSyntaxErrorKind};
2020

2121
use super::{FStringElementsKind, Parenthesized, RecoveryContextKind};
2222

@@ -2161,10 +2161,24 @@ impl<'src> Parser<'src> {
21612161

21622162
let value = self.parse_conditional_expression_or_higher();
21632163

2164+
let range = self.node_range(start);
2165+
2166+
// test_err walrus_before_py38
2167+
// # parse_options: { "target_version": "3.7" }
2168+
// if x := 1: ...
2169+
2170+
// test_ok walrus_after_py38
2171+
// # parse_options: { "target_version": "3.8" }
2172+
// if x := 1: ...
2173+
2174+
if self.options.target_version < PythonVersion::PY38 {
2175+
self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::WalrusBeforePy38, range);
2176+
}
2177+
21642178
ast::ExprNamed {
21652179
target: Box::new(target),
21662180
value: Box::new(value.expr),
2167-
range: self.node_range(start),
2181+
range,
21682182
}
21692183
}
21702184

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
source: crates/ruff_python_parser/tests/fixtures.rs
3+
input_file: crates/ruff_python_parser/resources/inline/err/walrus_before_py38.py
4+
---
5+
## AST
6+
7+
```
8+
Module(
9+
ModModule {
10+
range: 0..60,
11+
body: [
12+
If(
13+
StmtIf {
14+
range: 45..59,
15+
test: Named(
16+
ExprNamed {
17+
range: 48..54,
18+
target: Name(
19+
ExprName {
20+
range: 48..49,
21+
id: Name("x"),
22+
ctx: Store,
23+
},
24+
),
25+
value: NumberLiteral(
26+
ExprNumberLiteral {
27+
range: 53..54,
28+
value: Int(
29+
1,
30+
),
31+
},
32+
),
33+
},
34+
),
35+
body: [
36+
Expr(
37+
StmtExpr {
38+
range: 56..59,
39+
value: EllipsisLiteral(
40+
ExprEllipsisLiteral {
41+
range: 56..59,
42+
},
43+
),
44+
},
45+
),
46+
],
47+
elif_else_clauses: [],
48+
},
49+
),
50+
],
51+
},
52+
)
53+
```
54+
## Unsupported Syntax Errors
55+
56+
|
57+
1 | # parse_options: { "target_version": "3.7" }
58+
2 | if x := 1: ...
59+
| ^^^^^^ Syntax Error: Cannot use named assignment expression (`:=`) on Python 3.7 (syntax was added in Python 3.8)
60+
|
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
source: crates/ruff_python_parser/tests/fixtures.rs
3+
input_file: crates/ruff_python_parser/resources/inline/ok/walrus_after_py38.py
4+
---
5+
## AST
6+
7+
```
8+
Module(
9+
ModModule {
10+
range: 0..60,
11+
body: [
12+
If(
13+
StmtIf {
14+
range: 45..59,
15+
test: Named(
16+
ExprNamed {
17+
range: 48..54,
18+
target: Name(
19+
ExprName {
20+
range: 48..49,
21+
id: Name("x"),
22+
ctx: Store,
23+
},
24+
),
25+
value: NumberLiteral(
26+
ExprNumberLiteral {
27+
range: 53..54,
28+
value: Int(
29+
1,
30+
),
31+
},
32+
),
33+
},
34+
),
35+
body: [
36+
Expr(
37+
StmtExpr {
38+
range: 56..59,
39+
value: EllipsisLiteral(
40+
ExprEllipsisLiteral {
41+
range: 56..59,
42+
},
43+
),
44+
},
45+
),
46+
],
47+
elif_else_clauses: [],
48+
},
49+
),
50+
],
51+
},
52+
)
53+
```

0 commit comments

Comments
 (0)