Skip to content

Commit

Permalink
Add parentheses when simplifying type annotations in SIM222
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Sep 3, 2023
1 parent b57ddd5 commit 9b88d54
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 23 deletions.
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

0 comments on commit 9b88d54

Please sign in to comment.