Treat unary operations on constants as constant-like (#3348)

This commit is contained in:
Charlie Marsh 2023-03-04 16:30:33 -05:00 committed by GitHub
parent d7767b2bad
commit 51fe9f7d4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 132 additions and 44 deletions

View file

@ -6,6 +6,8 @@
"yoda" <= compare # SIM300 "yoda" <= compare # SIM300
"yoda" < compare # SIM300 "yoda" < compare # SIM300
42 > age # SIM300 42 > age # SIM300
-42 > age # SIM300
+42 > age # SIM300
YODA == age # SIM300 YODA == age # SIM300
YODA > age # SIM300 YODA > age # SIM300
YODA >= age # SIM300 YODA >= age # SIM300

View file

@ -11,6 +11,9 @@ if 10 == 100: # [comparison-of-constants] R0133
if 1 == 3: # [comparison-of-constants] R0133 if 1 == 3: # [comparison-of-constants] R0133
pass pass
if 1 == -3: # [comparison-of-constants] R0133
pass
x = 0 x = 0
if 4 == 3 == x: # [comparison-of-constants] R0133 if 4 == 3 == x: # [comparison-of-constants] R0133
pass pass
@ -35,6 +38,12 @@ if argc != 1: # correct
if argc != 2: # [magic-value-comparison] if argc != 2: # [magic-value-comparison]
pass pass
if argc != -2: # [magic-value-comparison]
pass
if argc != +2: # [magic-value-comparison]
pass
if __name__ == "__main__": # correct if __name__ == "__main__": # correct
pass pass

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use libcst_native::{Codegen, CodegenState, CompOp}; use libcst_native::{Codegen, CodegenState, CompOp};
use ruff_macros::{define_violation, derive_message_formats}; use ruff_macros::{define_violation, derive_message_formats};
use ruff_python::str::{self}; use ruff_python::str::{self};
use rustpython_parser::ast::{Cmpop, Expr, ExprKind}; use rustpython_parser::ast::{Cmpop, Expr, ExprKind, Unaryop};
use crate::ast::types::Range; use crate::ast::types::Range;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -50,6 +50,10 @@ fn is_constant_like(expr: &Expr) -> bool {
ExprKind::Constant { .. } => true, ExprKind::Constant { .. } => true,
ExprKind::Tuple { elts, .. } => elts.iter().all(is_constant_like), ExprKind::Tuple { elts, .. } => elts.iter().all(is_constant_like),
ExprKind::Name { id, .. } => str::is_upper(id), ExprKind::Name { id, .. } => str::is_upper(id),
ExprKind::UnaryOp {
op: Unaryop::UAdd | Unaryop::USub | Unaryop::Invert,
operand,
} => matches!(operand.node, ExprKind::Constant { .. }),
_ => false, _ => false,
} }
} }

View file

@ -130,110 +130,146 @@ expression: diagnostics
parent: ~ parent: ~
- kind: - kind:
YodaConditions: YodaConditions:
suggestion: age == YODA suggestion: age < -42
location: location:
row: 9 row: 9
column: 0 column: 0
end_location: end_location:
row: 9 row: 9
column: 11 column: 9
fix: fix:
content: age == YODA content: age < -42
location: location:
row: 9 row: 9
column: 0 column: 0
end_location: end_location:
row: 9 row: 9
column: 9
parent: ~
- kind:
YodaConditions:
suggestion: age < +42
location:
row: 10
column: 0
end_location:
row: 10
column: 9
fix:
content: age < +42
location:
row: 10
column: 0
end_location:
row: 10
column: 9
parent: ~
- kind:
YodaConditions:
suggestion: age == YODA
location:
row: 11
column: 0
end_location:
row: 11
column: 11
fix:
content: age == YODA
location:
row: 11
column: 0
end_location:
row: 11
column: 11 column: 11
parent: ~ parent: ~
- kind: - kind:
YodaConditions: YodaConditions:
suggestion: age < YODA suggestion: age < YODA
location: location:
row: 10 row: 12
column: 0 column: 0
end_location: end_location:
row: 10 row: 12
column: 10 column: 10
fix: fix:
content: age < YODA content: age < YODA
location: location:
row: 10 row: 12
column: 0 column: 0
end_location: end_location:
row: 10 row: 12
column: 10 column: 10
parent: ~ parent: ~
- kind: - kind:
YodaConditions: YodaConditions:
suggestion: age <= YODA suggestion: age <= YODA
location: location:
row: 11 row: 13
column: 0 column: 0
end_location: end_location:
row: 11 row: 13
column: 11 column: 11
fix: fix:
content: age <= YODA content: age <= YODA
location: location:
row: 11 row: 13
column: 0 column: 0
end_location: end_location:
row: 11 row: 13
column: 11 column: 11
parent: ~ parent: ~
- kind: - kind:
YodaConditions: YodaConditions:
suggestion: age == JediOrder.YODA suggestion: age == JediOrder.YODA
location: location:
row: 12 row: 14
column: 0 column: 0
end_location: end_location:
row: 12 row: 14
column: 21 column: 21
fix: fix:
content: age == JediOrder.YODA content: age == JediOrder.YODA
location: location:
row: 12 row: 14
column: 0 column: 0
end_location: end_location:
row: 12 row: 14
column: 21 column: 21
parent: ~ parent: ~
- kind: - kind:
YodaConditions: YodaConditions:
suggestion: (number - 100) > 0 suggestion: (number - 100) > 0
location: location:
row: 13 row: 15
column: 0 column: 0
end_location: end_location:
row: 13 row: 15
column: 18 column: 18
fix: fix:
content: (number - 100) > 0 content: (number - 100) > 0
location: location:
row: 13 row: 15
column: 0 column: 0
end_location: end_location:
row: 13 row: 15
column: 18 column: 18
parent: ~ parent: ~
- kind: - kind:
YodaConditions: YodaConditions:
suggestion: (60 * 60) < SomeClass().settings.SOME_CONSTANT_VALUE suggestion: (60 * 60) < SomeClass().settings.SOME_CONSTANT_VALUE
location: location:
row: 14 row: 16
column: 0 column: 0
end_location: end_location:
row: 14 row: 16
column: 52 column: 52
fix: fix:
content: (60 * 60) < SomeClass().settings.SOME_CONSTANT_VALUE content: (60 * 60) < SomeClass().settings.SOME_CONSTANT_VALUE
location: location:
row: 14 row: 16
column: 0 column: 0
end_location: end_location:
row: 14 row: 16
column: 52 column: 52
parent: ~ parent: ~

View file

@ -1,8 +1,9 @@
use itertools::Itertools; use itertools::Itertools;
use ruff_macros::{define_violation, derive_message_formats}; use rustpython_parser::ast::{Constant, Expr, ExprKind, Unaryop};
use rustpython_parser::ast::{Constant, Expr, ExprKind};
use crate::ast::helpers::unparse_constant; use ruff_macros::{define_violation, derive_message_formats};
use crate::ast::helpers::unparse_expr;
use crate::ast::types::Range; use crate::ast::types::Range;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::Diagnostic; use crate::registry::Diagnostic;
@ -24,6 +25,22 @@ impl Violation for MagicValueComparison {
} }
} }
/// If an [`Expr`] is a constant (or unary operation on a constant), return the [`Constant`].
fn as_constant(expr: &Expr) -> Option<&Constant> {
match &expr.node {
ExprKind::Constant { value, .. } => Some(value),
ExprKind::UnaryOp {
op: Unaryop::UAdd | Unaryop::USub | Unaryop::Invert,
operand,
} => match &operand.node {
ExprKind::Constant { value, .. } => Some(value),
_ => None,
},
_ => None,
}
}
/// Return `true` if a [`Constant`] is a magic value.
fn is_magic_value(constant: &Constant, allowed_types: &[ConstantType]) -> bool { fn is_magic_value(constant: &Constant, allowed_types: &[ConstantType]) -> bool {
if let Ok(constant_type) = ConstantType::try_from(constant) { if let Ok(constant_type) = ConstantType::try_from(constant) {
if allowed_types.contains(&constant_type) { if allowed_types.contains(&constant_type) {
@ -37,7 +54,7 @@ fn is_magic_value(constant: &Constant, allowed_types: &[ConstantType]) -> bool {
Constant::Ellipsis => false, Constant::Ellipsis => false,
// Otherwise, special-case some common string and integer types. // Otherwise, special-case some common string and integer types.
Constant::Str(value) => !matches!(value.as_str(), "" | "__main__"), Constant::Str(value) => !matches!(value.as_str(), "" | "__main__"),
Constant::Int(value) => !matches!(value.try_into(), Ok(-1 | 0 | 1)), Constant::Int(value) => !matches!(value.try_into(), Ok(0 | 1)),
Constant::Bytes(_) => true, Constant::Bytes(_) => true,
Constant::Tuple(_) => true, Constant::Tuple(_) => true,
Constant::Float(_) => true, Constant::Float(_) => true,
@ -53,19 +70,17 @@ pub fn magic_value_comparison(checker: &mut Checker, left: &Expr, comparators: &
{ {
// If both of the comparators are constant, skip rule for the whole expression. // If both of the comparators are constant, skip rule for the whole expression.
// R0133: comparison-of-constants // R0133: comparison-of-constants
if matches!(left.node, ExprKind::Constant { .. }) if as_constant(left).is_some() && as_constant(right).is_some() {
&& matches!(right.node, ExprKind::Constant { .. })
{
return; return;
} }
} }
for comparison_expr in std::iter::once(left).chain(comparators.iter()) { for comparison_expr in std::iter::once(left).chain(comparators.iter()) {
if let ExprKind::Constant { value, .. } = &comparison_expr.node { if let Some(value) = as_constant(comparison_expr) {
if is_magic_value(value, &checker.settings.pylint.allow_magic_value_types) { if is_magic_value(value, &checker.settings.pylint.allow_magic_value_types) {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
MagicValueComparison { MagicValueComparison {
value: unparse_constant(value, checker.stylist), value: unparse_expr(comparison_expr, checker.stylist),
}, },
Range::from_located(comparison_expr), Range::from_located(comparison_expr),
)); ));

View file

@ -1,5 +1,5 @@
--- ---
source: src/rules/pylint/mod.rs source: crates/ruff/src/rules/pylint/mod.rs
expression: diagnostics expression: diagnostics
--- ---
- kind: - kind:
@ -17,21 +17,43 @@ expression: diagnostics
MagicValueComparison: MagicValueComparison:
value: "2" value: "2"
location: location:
row: 35 row: 38
column: 11 column: 11
end_location: end_location:
row: 35 row: 38
column: 12 column: 12
fix: ~ fix: ~
parent: ~ parent: ~
- kind:
MagicValueComparison:
value: "-2"
location:
row: 41
column: 11
end_location:
row: 41
column: 13
fix: ~
parent: ~
- kind:
MagicValueComparison:
value: "+2"
location:
row: 44
column: 11
end_location:
row: 44
column: 13
fix: ~
parent: ~
- kind: - kind:
MagicValueComparison: MagicValueComparison:
value: "3.141592653589793" value: "3.141592653589793"
location: location:
row: 56 row: 65
column: 20 column: 20
end_location: end_location:
row: 56 row: 65
column: 40 column: 40
fix: ~ fix: ~
parent: ~ parent: ~

View file

@ -6,10 +6,10 @@ expression: diagnostics
MagicValueComparison: MagicValueComparison:
value: "\"Hunter2\"" value: "\"Hunter2\""
location: location:
row: 50 row: 59
column: 21 column: 21
end_location: end_location:
row: 50 row: 59
column: 30 column: 30
fix: ~ fix: ~
parent: ~ parent: ~
@ -17,10 +17,10 @@ expression: diagnostics
MagicValueComparison: MagicValueComparison:
value: "3.141592653589793" value: "3.141592653589793"
location: location:
row: 56 row: 65
column: 20 column: 20
end_location: end_location:
row: 56 row: 65
column: 40 column: 40
fix: ~ fix: ~
parent: ~ parent: ~
@ -28,10 +28,10 @@ expression: diagnostics
MagicValueComparison: MagicValueComparison:
value: "b\"something\"" value: "b\"something\""
location: location:
row: 65 row: 74
column: 17 column: 17
end_location: end_location:
row: 65 row: 74
column: 29 column: 29
fix: ~ fix: ~
parent: ~ parent: ~