Skip to content

Commit

Permalink
[flake8-simplify] Detect implicit else cases in needless-bool (…
Browse files Browse the repository at this point in the history
…`SIM103`) (#10414)

Fixes #10402 

## Summary

For SIM103, detect and simplify the following case:

[playground
link](https://play.ruff.rs/d98570aa-b180-495b-8600-5c4c3fd02526)
```python
def main():
    if foo > 5:
        return True
    return False
```

## Test Plan

Unit tested only.
  • Loading branch information
ottaviohartman committed Mar 18, 2024
1 parent 229a50a commit 526abeb
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,22 @@ def bool():
return True
else:
return False


###
# Positive cases (preview)
###


def f():
# SIM103
if a:
return True
return False


def f():
# SIM103
if a:
return False
return True
2 changes: 1 addition & 1 deletion crates/ruff_linter/src/checkers/ast/analyze/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
flake8_simplify::rules::if_with_same_arms(checker, if_);
}
if checker.enabled(Rule::NeedlessBool) {
flake8_simplify::rules::needless_bool(checker, if_);
flake8_simplify::rules::needless_bool(checker, stmt);
}
if checker.enabled(Rule::IfElseBlockInsteadOfDictLookup) {
flake8_simplify::rules::if_else_block_instead_of_dict_lookup(checker, if_);
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/rules/flake8_simplify/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ mod tests {
Ok(())
}

#[test_case(Rule::NeedlessBool, Path::new("SIM103.py"))]
#[test_case(Rule::YodaConditions, Path::new("SIM300.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::traversal;
use ruff_python_ast::{self as ast, Arguments, ElifElseClause, Expr, ExprContext, Stmt};
use ruff_python_semantic::analyze::typing::{is_sys_version_block, is_type_checking_block};
use ruff_text_size::{Ranged, TextRange};
Expand All @@ -16,19 +17,28 @@ use crate::fix::snippet::SourceCodeSnippet;
///
/// ## Example
/// ```python
/// if foo:
/// if x > 0:
/// return True
/// else:
/// return False
/// ```
///
/// Use instead:
/// ```python
/// return bool(foo)
/// return x > 0
/// ```
///
/// In [preview], this rule will also flag implicit `else` cases, as in:
/// ```python
/// if x > 0:
/// return True
/// return False
/// ```
///
/// ## References
/// - [Python documentation: Truth Value Testing](https://docs.python.org/3/library/stdtypes.html#truth-value-testing)
///
/// [preview]: https://docs.astral.sh/ruff/preview/
#[violation]
pub struct NeedlessBool {
condition: SourceCodeSnippet,
Expand Down Expand Up @@ -62,23 +72,41 @@ impl Violation for NeedlessBool {
}

/// SIM103
pub(crate) fn needless_bool(checker: &mut Checker, stmt_if: &ast::StmtIf) {
pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) {
let Stmt::If(stmt_if) = stmt else { return };
let ast::StmtIf {
test: if_test,
body: if_body,
elif_else_clauses,
range: _,
..
} = stmt_if;

// Extract an `if` or `elif` (that returns) followed by an else (that returns the same value)
let (if_test, if_body, else_body, range) = match elif_else_clauses.as_slice() {
// if-else case
// if-else case:
// ```python
// if x > 0:
// return True
// else:
// return False
// ```
[ElifElseClause {
body: else_body,
test: None,
..
}] => (if_test.as_ref(), if_body, else_body, stmt_if.range()),
}] => (
if_test.as_ref(),
if_body,
else_body.as_slice(),
stmt_if.range(),
),
// elif-else case
// ```python
// if x > 0:
// return True
// elif x < 0:
// return False
// ```
[.., ElifElseClause {
body: elif_body,
test: Some(elif_test),
Expand All @@ -90,12 +118,47 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt_if: &ast::StmtIf) {
}] => (
elif_test,
elif_body,
else_body,
else_body.as_slice(),
TextRange::new(elif_range.start(), else_range.end()),
),
// if-implicit-else case:
// ```python
// if x > 0:
// return True
// return False
// ```
[] if checker.settings.preview.is_enabled() => {
// Fetching the next sibling is expensive, so do some validation early.
if is_one_line_return_bool(if_body).is_none() {
return;
}

// Fetch the next sibling statement.
let Some(next_stmt) = checker
.semantic()
.current_statement_parent()
.and_then(|parent| traversal::suite(stmt, parent))
.and_then(|suite| traversal::next_sibling(stmt, suite))
else {
return;
};

// If the next sibling is not a return statement, abort.
if !next_stmt.is_return_stmt() {
return;
}

(
if_test.as_ref(),
if_body,
std::slice::from_ref(next_stmt),
TextRange::new(stmt_if.start(), next_stmt.end()),
)
}
_ => return,
};

// Both branches must be one-liners that return a boolean.
let (Some(if_return), Some(else_return)) = (
is_one_line_return_bool(if_body),
is_one_line_return_bool(else_body),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,3 @@ SIM103.py:83:5: SIM103 Return the condition `a` directly
| |____________________^ SIM103
|
= help: Inline condition


Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
---
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
---
SIM103.py:3:5: SIM103 [*] Return the condition `a` directly
|
1 | def f():
2 | # SIM103
3 | if a:
| _____^
4 | | return True
5 | | else:
6 | | return False
| |____________________^ SIM103
|
= help: Replace with `return bool(a)`

Unsafe fix
1 1 | def f():
2 2 | # SIM103
3 |- if a:
4 |- return True
5 |- else:
6 |- return False
3 |+ return bool(a)
7 4 |
8 5 |
9 6 | def f():

SIM103.py:11:5: SIM103 [*] Return the condition `a == b` directly
|
9 | def f():
10 | # SIM103
11 | if a == b:
| _____^
12 | | return True
13 | | else:
14 | | return False
| |____________________^ SIM103
|
= help: Replace with `return a == b`

Unsafe fix
8 8 |
9 9 | def f():
10 10 | # SIM103
11 |- if a == b:
12 |- return True
13 |- else:
14 |- return False
11 |+ return a == b
15 12 |
16 13 |
17 14 | def f():

SIM103.py:21:5: SIM103 [*] Return the condition `b` directly
|
19 | if a:
20 | return 1
21 | elif b:
| _____^
22 | | return True
23 | | else:
24 | | return False
| |____________________^ SIM103
|
= help: Replace with `return bool(b)`

Unsafe fix
18 18 | # SIM103
19 19 | if a:
20 20 | return 1
21 |- elif b:
22 |- return True
23 |- else:
24 |- return False
21 |+ return bool(b)
25 22 |
26 23 |
27 24 | def f():

SIM103.py:32:9: SIM103 [*] Return the condition `b` directly
|
30 | return 1
31 | else:
32 | if b:
| _________^
33 | | return True
34 | | else:
35 | | return False
| |________________________^ SIM103
|
= help: Replace with `return bool(b)`

Unsafe fix
29 29 | if a:
30 30 | return 1
31 31 | else:
32 |- if b:
33 |- return True
34 |- else:
35 |- return False
32 |+ return bool(b)
36 33 |
37 34 |
38 35 | def f():

SIM103.py:57:5: SIM103 [*] Return the condition `a` directly
|
55 | def f():
56 | # SIM103 (but not fixable)
57 | if a:
| _____^
58 | | return False
59 | | else:
60 | | return True
| |___________________^ SIM103
|
= help: Replace with `return not a`

Unsafe fix
54 54 |
55 55 | def f():
56 56 | # SIM103 (but not fixable)
57 |- if a:
58 |- return False
59 |- else:
60 |- return True
57 |+ return not a
61 58 |
62 59 |
63 60 | def f():

SIM103.py:83:5: SIM103 Return the condition `a` directly
|
81 | def bool():
82 | return False
83 | if a:
| _____^
84 | | return True
85 | | else:
86 | | return False
| |____________________^ SIM103
|
= help: Inline condition

SIM103.py:96:5: SIM103 [*] Return the condition `a` directly
|
94 | def f():
95 | # SIM103
96 | if a:
| _____^
97 | | return True
98 | | return False
| |________________^ SIM103
|
= help: Replace with `return bool(a)`

Unsafe fix
93 93 |
94 94 | def f():
95 95 | # SIM103
96 |- if a:
97 |- return True
98 |- return False
96 |+ return bool(a)
99 97 |
100 98 |
101 99 | def f():

SIM103.py:103:5: SIM103 [*] Return the condition `a` directly
|
101 | def f():
102 | # SIM103
103 | if a:
| _____^
104 | | return False
105 | | return True
| |_______________^ SIM103
|
= help: Replace with `return not a`

Unsafe fix
100 100 |
101 101 | def f():
102 102 | # SIM103
103 |- if a:
104 |- return False
105 |- return True
103 |+ return not a

0 comments on commit 526abeb

Please sign in to comment.