Skip to content

Commit 7faa431

Browse files
authored
New rule: Prevent assignment expressions in assert statements (#7856)
1 parent 74b00c9 commit 7faa431

File tree

9 files changed

+93
-0
lines changed

9 files changed

+93
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# RUF018
2+
assert (x := 0) == 0
3+
assert x, (y := "error")
4+
5+
# OK
6+
if z := 0:
7+
pass

crates/ruff_linter/src/checkers/ast/analyze/expression.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,11 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
14131413
pylint::rules::repeated_equality_comparison(checker, bool_op);
14141414
}
14151415
}
1416+
Expr::NamedExpr(..) => {
1417+
if checker.enabled(Rule::AssignmentInAssert) {
1418+
ruff::rules::assignment_in_assert(checker, expr);
1419+
}
1420+
}
14161421
_ => {}
14171422
};
14181423
}

crates/ruff_linter/src/codes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
865865
(Ruff, "016") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidIndexType),
866866
#[allow(deprecated)]
867867
(Ruff, "017") => (RuleGroup::Nursery, rules::ruff::rules::QuadraticListSummation),
868+
(Ruff, "018") => (RuleGroup::Preview, rules::ruff::rules::AssignmentInAssert),
868869
(Ruff, "100") => (RuleGroup::Unspecified, rules::ruff::rules::UnusedNOQA),
869870
(Ruff, "200") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidPyprojectToml),
870871

crates/ruff_linter/src/rules/ruff/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ mod tests {
4242
)]
4343
#[test_case(Rule::QuadraticListSummation, Path::new("RUF017_1.py"))]
4444
#[test_case(Rule::QuadraticListSummation, Path::new("RUF017_0.py"))]
45+
#[test_case(Rule::AssignmentInAssert, Path::new("RUF018.py"))]
4546
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
4647
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
4748
let diagnostics = test_path(
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use ruff_python_ast::Expr;
2+
3+
use ruff_diagnostics::{Diagnostic, Violation};
4+
use ruff_macros::{derive_message_formats, violation};
5+
use ruff_text_size::Ranged;
6+
7+
use crate::checkers::ast::Checker;
8+
9+
/// ## What it does
10+
/// Checks for named assignment expressions (e.g., `x := 0`) in `assert`
11+
/// statements.
12+
///
13+
/// ## Why is this bad?
14+
/// Named assignment expressions (also known as "walrus operators") are used to
15+
/// assign a value to a variable as part of a larger expression.
16+
///
17+
/// Named assignments are syntactically valid in `assert` statements. However,
18+
/// when the Python interpreter is run under the `-O` flag, `assert` statements
19+
/// are not executed. In this case, the named assignment will also be ignored,
20+
/// which may result in unexpected behavior (e.g., undefined variable
21+
/// accesses).
22+
///
23+
/// ## Examples
24+
/// ```python
25+
/// assert (x := 0) == 0
26+
/// ```
27+
///
28+
/// Use instead:
29+
/// ```python
30+
/// x = 0
31+
/// assert x == 0
32+
/// ```
33+
///
34+
/// ## References
35+
/// - [Python documentation: `-O`](https://docs.python.org/3/using/cmdline.html#cmdoption-O)
36+
#[violation]
37+
pub struct AssignmentInAssert;
38+
39+
impl Violation for AssignmentInAssert {
40+
#[derive_message_formats]
41+
fn message(&self) -> String {
42+
format!("Avoid assignment expressions in `assert` statements")
43+
}
44+
}
45+
46+
/// RUF018
47+
pub(crate) fn assignment_in_assert(checker: &mut Checker, value: &Expr) {
48+
if checker.semantic().current_statement().is_assert_stmt() {
49+
checker
50+
.diagnostics
51+
.push(Diagnostic::new(AssignmentInAssert, value.range()));
52+
}
53+
}

crates/ruff_linter/src/rules/ruff/rules/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub(crate) use ambiguous_unicode_character::*;
2+
pub(crate) use assignment_in_assert::*;
23
pub(crate) use asyncio_dangling_task::*;
34
pub(crate) use collection_literal_concatenation::*;
45
pub(crate) use explicit_f_string_type_conversion::*;
@@ -16,6 +17,7 @@ pub(crate) use unreachable::*;
1617
pub(crate) use unused_noqa::*;
1718

1819
mod ambiguous_unicode_character;
20+
mod assignment_in_assert;
1921
mod asyncio_dangling_task;
2022
mod collection_literal_concatenation;
2123
mod confusables;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: crates/ruff_linter/src/rules/ruff/mod.rs
3+
---
4+
RUF018.py:2:9: RUF018 Avoid assignment expressions in `assert` statements
5+
|
6+
1 | # RUF018
7+
2 | assert (x := 0) == 0
8+
| ^^^^^^ RUF018
9+
3 | assert x, (y := "error")
10+
|
11+
12+
RUF018.py:3:12: RUF018 Avoid assignment expressions in `assert` statements
13+
|
14+
1 | # RUF018
15+
2 | assert (x := 0) == 0
16+
3 | assert x, (y := "error")
17+
| ^^^^^^^^^^^^ RUF018
18+
4 |
19+
5 | # OK
20+
|
21+
22+

crates/ruff_workspace/src/configuration.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,7 @@ mod tests {
10471047
Rule::TooManyPublicMethods,
10481048
Rule::UndocumentedWarn,
10491049
Rule::UnnecessaryEnumerate,
1050+
Rule::AssignmentInAssert,
10501051
];
10511052

10521053
#[allow(clippy::needless_pass_by_value)]

ruff.schema.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)