Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parentheses when simplifying conditions in SIM222 #7117

Merged
merged 1 commit into from
Sep 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_simplify/SIM222.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,11 @@ def validate(self, value):

if f(a or [1] or True or [2]): # SIM222
pass

# Regression test for: https://github.com/astral-sh/ruff/issues/7099
def secondToTime(s0: int) -> (int, int, int) or str:
m, s = divmod(s0, 60)


def secondToTime(s0: int) -> ((int, int, int) or str):
m, s = divmod(s0, 60)
49 changes: 33 additions & 16 deletions crates/ruff/src/rules/flake8_simplify/rules/ast_bool_op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, AutofixKind, Diagnostic, Edit
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::helpers::{contains_effect, Truthiness};
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_codegen::Generator;

use crate::checkers::ast::Checker;
use crate::registry::AsRule;
Expand Down Expand Up @@ -692,12 +694,12 @@ pub(crate) fn expr_or_not_expr(checker: &mut Checker, expr: &Expr) {
}
}

pub(crate) fn get_short_circuit_edit(
fn get_short_circuit_edit(
expr: &Expr,
range: TextRange,
truthiness: Truthiness,
in_boolean_test: bool,
checker: &Checker,
generator: Generator,
) -> Edit {
let content = if in_boolean_test {
match truthiness {
Expand All @@ -708,9 +710,17 @@ pub(crate) fn get_short_circuit_edit(
}
}
} else {
checker.generator().expr(expr)
generator.expr(expr)
};
Edit::range_replacement(content, range)
Edit::range_replacement(
if matches!(expr, Expr::Tuple(ast::ExprTuple { elts, ctx: _, range: _}) if !elts.is_empty())
{
format!("({content})")
} else {
content
},
range,
)
}

fn is_short_circuit(
Expand All @@ -734,7 +744,7 @@ fn is_short_circuit(
BoolOp::Or => Truthiness::Truthy,
};

let mut location = expr.start();
let mut furthest = expr;
let mut edit = None;
let mut remove = None;

Expand All @@ -749,7 +759,7 @@ fn is_short_circuit(
&& (!checker.semantic().in_boolean_test()
|| contains_effect(value, |id| checker.semantic().is_builtin(id)))
{
location = next_value.start();
furthest = next_value;
continue;
}

Expand All @@ -758,35 +768,42 @@ fn is_short_circuit(
// short-circuit expression is the first expression in the list; otherwise, we'll see it
// as `next_value` before we see it as `value`.
if value_truthiness == short_circuit_truthiness {
remove = Some(if location == value.start() {
ContentAround::After
} else {
ContentAround::Both
});
remove = Some(ContentAround::After);

edit = Some(get_short_circuit_edit(
value,
TextRange::new(location, expr.end()),
TextRange::new(
parenthesized_range(furthest.into(), expr.into(), checker.locator().contents())
.unwrap_or(furthest.range())
.start(),
expr.end(),
),
short_circuit_truthiness,
checker.semantic().in_boolean_test(),
checker,
checker.generator(),
));
break;
}

// If the next expression is a constant, and it matches the short-circuit value, then
// we can return the location of the expression.
if next_value_truthiness == short_circuit_truthiness {
remove = Some(if index == values.len() - 2 {
remove = Some(if index + 1 == values.len() - 1 {
ContentAround::Before
} else {
ContentAround::Both
});
edit = Some(get_short_circuit_edit(
next_value,
TextRange::new(location, expr.end()),
TextRange::new(
parenthesized_range(furthest.into(), expr.into(), checker.locator().contents())
.unwrap_or(furthest.range())
.start(),
expr.end(),
),
short_circuit_truthiness,
checker.semantic().in_boolean_test(),
checker,
checker.generator(),
));
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ SIM222.py:85:6: SIM222 [*] Use `True` instead of `... or True`
87 87 | a or (1,) or True or (2,) # SIM222
88 88 |

SIM222.py:87:6: SIM222 [*] Use `1,` instead of `1, or ...`
SIM222.py:87:6: SIM222 [*] Use `(1,)` instead of `(1,) or ...`
|
85 | a or tuple(()) or True # SIM222
86 |
Expand All @@ -543,14 +543,14 @@ SIM222.py:87:6: SIM222 [*] Use `1,` instead of `1, or ...`
88 |
89 | a or tuple((1,)) or True or tuple((2,)) # SIM222
|
= help: Replace with `1,`
= help: Replace with `(1,)`

ℹ Suggested fix
84 84 |
85 85 | a or tuple(()) or True # SIM222
86 86 |
87 |-a or (1,) or True or (2,) # SIM222
87 |+a or 1, # SIM222
87 |+a or (1,) # SIM222
88 88 |
89 89 | a or tuple((1,)) or True or tuple((2,)) # SIM222
90 90 |
Expand Down Expand Up @@ -1003,5 +1003,42 @@ SIM222.py:153:11: SIM222 [*] Use `[1]` instead of `[1] or ...`
153 |-if f(a or [1] or True or [2]): # SIM222
153 |+if f(a or [1]): # SIM222
154 154 | pass
155 155 |
156 156 | # Regression test for: https://github.com/astral-sh/ruff/issues/7099

SIM222.py:157:30: SIM222 [*] Use `(int, int, int)` instead of `(int, int, int) or ...`
|
156 | # Regression test for: https://github.com/astral-sh/ruff/issues/7099
157 | def secondToTime(s0: int) -> (int, int, int) or str:
| ^^^^^^^^^^^^^^^^^^^^^^ SIM222
158 | m, s = divmod(s0, 60)
|
= help: Replace with `(int, int, int)`

ℹ Suggested fix
154 154 | pass
155 155 |
156 156 | # Regression test for: https://github.com/astral-sh/ruff/issues/7099
157 |-def secondToTime(s0: int) -> (int, int, int) or str:
157 |+def secondToTime(s0: int) -> (int, int, int):
158 158 | m, s = divmod(s0, 60)
159 159 |
160 160 |

SIM222.py:161:31: SIM222 [*] Use `(int, int, int)` instead of `(int, int, int) or ...`
|
161 | def secondToTime(s0: int) -> ((int, int, int) or str):
| ^^^^^^^^^^^^^^^^^^^^^^ SIM222
162 | m, s = divmod(s0, 60)
|
= help: Replace with `(int, int, int)`

ℹ Suggested fix
158 158 | m, s = divmod(s0, 60)
159 159 |
160 160 |
161 |-def secondToTime(s0: int) -> ((int, int, int) or str):
161 |+def secondToTime(s0: int) -> ((int, int, int)):
162 162 | m, s = divmod(s0, 60)


7 changes: 3 additions & 4 deletions crates/ruff_python_codegen/src/generator.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
//! Generate Python source code from an abstract syntax tree (AST).

use ruff_python_ast::{ParameterWithDefault, TypeParams};
use std::ops::Deref;

use ruff_python_ast::{
self as ast, Alias, BoolOp, CmpOp, Comprehension, Constant, ConversionFlag, DebugText,
ExceptHandler, Expr, Identifier, MatchCase, Operator, Parameter, Parameters, Pattern, Stmt,
Suite, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, WithItem,
};
use ruff_python_ast::{ParameterWithDefault, TypeParams};
use ruff_python_literal::escape::{AsciiEscape, Escape, UnicodeEscape};

use ruff_source_file::LineEnding;

use super::stylist::{Indentation, Quote, Stylist};
Expand Down Expand Up @@ -1381,12 +1380,12 @@ impl<'a> Generator<'a> {
mod tests {
use ruff_python_ast::{Mod, ModModule};
use ruff_python_parser::{self, parse_suite, Mode};

use ruff_source_file::LineEnding;

use super::Generator;
use crate::stylist::{Indentation, Quote};

use super::Generator;

fn round_trip(contents: &str) -> String {
let indentation = Indentation::default();
let quote = Quote::default();
Expand Down