Skip to content

Commit 842f882

Browse files
[ruff] Avoid reporting when ndigits is possibly negative (RUF057) (#15234)
1 parent 75015b0 commit 842f882

File tree

4 files changed

+141
-285
lines changed

4 files changed

+141
-285
lines changed

crates/ruff_linter/resources/test/fixtures/ruff/RUF057.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@
66
round(42) # Error (safe)
77
round(42, None) # Error (safe)
88
round(42, 2) # Error (safe)
9-
round(42, inferred_int) # Error (safe)
10-
round(42, 3 + 4) # Error (safe)
11-
round(42, foo) # Error (unsafe)
9+
round(42, -2) # No error
10+
round(42, inferred_int) # No error
11+
round(42, 3 + 4) # No error
12+
round(42, foo) # No error
1213

1314

1415
round(42.) # No error
1516
round(42., None) # No error
1617
round(42., 2) # No error
18+
round(42., -2) # No error
1719
round(42., inferred_int) # No error
1820
round(42., 3 + 4) # No error
1921
round(42., foo) # No error
@@ -22,14 +24,16 @@
2224
round(4 + 2) # Error (safe)
2325
round(4 + 2, None) # Error (safe)
2426
round(4 + 2, 2) # Error (safe)
25-
round(4 + 2, inferred_int) # Error (safe)
26-
round(4 + 2, 3 + 4) # Error (safe)
27-
round(4 + 2, foo) # Error (unsafe)
27+
round(4 + 2, -2) # No error
28+
round(4 + 2, inferred_int) # No error
29+
round(4 + 2, 3 + 4) # No error
30+
round(4 + 2, foo) # No error
2831

2932

3033
round(4. + 2.) # No error
3134
round(4. + 2., None) # No error
3235
round(4. + 2., 2) # No error
36+
round(4. + 2., -2) # No error
3337
round(4. + 2., inferred_int) # No error
3438
round(4. + 2., 3 + 4) # No error
3539
round(4. + 2., foo) # No error
@@ -38,14 +42,16 @@
3842
round(inferred_int) # Error (unsafe)
3943
round(inferred_int, None) # Error (unsafe)
4044
round(inferred_int, 2) # Error (unsafe)
41-
round(inferred_int, inferred_int) # Error (unsafe)
42-
round(inferred_int, 3 + 4) # Error (unsafe)
45+
round(inferred_int, -2) # No error
46+
round(inferred_int, inferred_int) # No error
47+
round(inferred_int, 3 + 4) # No error
4348
round(inferred_int, foo) # No error
4449

4550

4651
round(inferred_float) # No error
4752
round(inferred_float, None) # No error
4853
round(inferred_float, 2) # No error
54+
round(inferred_float, -2) # No error
4955
round(inferred_float, inferred_int) # No error
5056
round(inferred_float, 3 + 4) # No error
5157
round(inferred_float, foo) # No error
@@ -54,6 +60,7 @@
5460
round(lorem) # No error
5561
round(lorem, None) # No error
5662
round(lorem, 2) # No error
63+
round(lorem, -2) # No error
5764
round(lorem, inferred_int) # No error
5865
round(lorem, 3 + 4) # No error
5966
round(lorem, foo) # No error

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

+12-11
Original file line numberDiff line numberDiff line change
@@ -179,37 +179,38 @@ fn round_applicability(arguments: &Arguments, semantic: &SemanticModel) -> Optio
179179

180180
match (rounded_value, ndigits_value) {
181181
// ```python
182+
// int(round(2, -1))
182183
// int(round(2, 0))
183184
// int(round(2))
184185
// int(round(2, None))
185186
// ```
186187
(
187188
RoundedValue::Int(InferredType::Equivalent),
188-
NdigitsValue::Int(InferredType::Equivalent)
189-
| NdigitsValue::NotGiven
190-
| NdigitsValue::LiteralNone,
189+
NdigitsValue::LiteralInt { .. }
190+
| NdigitsValue::Int(InferredType::Equivalent)
191+
| NdigitsValue::NotGivenOrNone,
191192
) => Some(Applicability::Safe),
192193

193194
// ```python
194195
// int(round(2.0))
195196
// int(round(2.0, None))
196197
// ```
197-
(
198-
RoundedValue::Float(InferredType::Equivalent),
199-
NdigitsValue::NotGiven | NdigitsValue::LiteralNone,
200-
) => Some(Applicability::Safe),
198+
(RoundedValue::Float(InferredType::Equivalent), NdigitsValue::NotGivenOrNone) => {
199+
Some(Applicability::Safe)
200+
}
201201

202202
// ```python
203203
// a: int = 2 # or True
204+
// int(round(a, -2))
204205
// int(round(a, 1))
205206
// int(round(a))
206207
// int(round(a, None))
207208
// ```
208209
(
209210
RoundedValue::Int(InferredType::AssignableTo),
210-
NdigitsValue::Int(InferredType::Equivalent)
211-
| NdigitsValue::NotGiven
212-
| NdigitsValue::LiteralNone,
211+
NdigitsValue::LiteralInt { .. }
212+
| NdigitsValue::Int(InferredType::Equivalent)
213+
| NdigitsValue::NotGivenOrNone,
213214
) => Some(Applicability::Unsafe),
214215

215216
// ```python
@@ -220,7 +221,7 @@ fn round_applicability(arguments: &Arguments, semantic: &SemanticModel) -> Optio
220221
// ```
221222
(
222223
RoundedValue::Float(InferredType::AssignableTo) | RoundedValue::Other,
223-
NdigitsValue::NotGiven | NdigitsValue::LiteralNone,
224+
NdigitsValue::NotGivenOrNone,
224225
) => Some(Applicability::Unsafe),
225226

226227
_ => None,

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

+23-14
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::checkers::ast::Checker;
22
use crate::Locator;
33
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix};
44
use ruff_macros::{derive_message_formats, ViolationMetadata};
5-
use ruff_python_ast::{Arguments, Expr, ExprCall};
5+
use ruff_python_ast::{Arguments, Expr, ExprCall, ExprNumberLiteral, Number};
66
use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType};
77
use ruff_python_semantic::analyze::typing;
88
use ruff_python_semantic::SemanticModel;
@@ -52,14 +52,14 @@ pub(crate) fn unnecessary_round(checker: &mut Checker, call: &ExprCall) {
5252
return;
5353
};
5454

55-
let applicability = match (rounded_value, ndigits_value) {
56-
// ```python
57-
// rounded(1, unknown)
58-
// ```
59-
(RoundedValue::Int(InferredType::Equivalent), NdigitsValue::Other) => Applicability::Unsafe,
60-
61-
(_, NdigitsValue::Other) => return,
55+
if !matches!(
56+
ndigits_value,
57+
NdigitsValue::NotGivenOrNone | NdigitsValue::LiteralInt { is_negative: false }
58+
) {
59+
return;
60+
}
6261

62+
let applicability = match rounded_value {
6363
// ```python
6464
// some_int: int
6565
//
@@ -69,7 +69,7 @@ pub(crate) fn unnecessary_round(checker: &mut Checker, call: &ExprCall) {
6969
// rounded(1, 4 + 2)
7070
// rounded(1, some_int)
7171
// ```
72-
(RoundedValue::Int(InferredType::Equivalent), _) => Applicability::Safe,
72+
RoundedValue::Int(InferredType::Equivalent) => Applicability::Safe,
7373

7474
// ```python
7575
// some_int: int
@@ -81,7 +81,7 @@ pub(crate) fn unnecessary_round(checker: &mut Checker, call: &ExprCall) {
8181
// rounded(some_int, 4 + 2)
8282
// rounded(some_int, some_other_int)
8383
// ```
84-
(RoundedValue::Int(InferredType::AssignableTo), _) => Applicability::Unsafe,
84+
RoundedValue::Int(InferredType::AssignableTo) => Applicability::Unsafe,
8585

8686
_ => return,
8787
};
@@ -113,8 +113,8 @@ pub(super) enum RoundedValue {
113113
/// The type of the second argument to `round()`
114114
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
115115
pub(super) enum NdigitsValue {
116-
NotGiven,
117-
LiteralNone,
116+
NotGivenOrNone,
117+
LiteralInt { is_negative: bool },
118118
Int(InferredType),
119119
Other,
120120
}
@@ -157,8 +157,7 @@ pub(super) fn rounded_and_ndigits<'a>(
157157
};
158158

159159
let ndigits_kind = match ndigits {
160-
None => NdigitsValue::NotGiven,
161-
Some(Expr::NoneLiteral(_)) => NdigitsValue::LiteralNone,
160+
None | Some(Expr::NoneLiteral(_)) => NdigitsValue::NotGivenOrNone,
162161

163162
Some(Expr::Name(name)) => {
164163
match semantic.only_binding(name).map(|id| semantic.binding(id)) {
@@ -169,6 +168,16 @@ pub(super) fn rounded_and_ndigits<'a>(
169168
}
170169
}
171170

171+
Some(Expr::NumberLiteral(ExprNumberLiteral {
172+
value: Number::Int(int),
173+
..
174+
})) => match int.as_i64() {
175+
None => NdigitsValue::Int(InferredType::Equivalent),
176+
Some(value) => NdigitsValue::LiteralInt {
177+
is_negative: value < 0,
178+
},
179+
},
180+
172181
Some(ndigits) => match ResolvedPythonType::from(ndigits) {
173182
ResolvedPythonType::Atom(PythonType::Number(NumberLike::Integer)) => {
174183
NdigitsValue::Int(InferredType::Equivalent)

0 commit comments

Comments
 (0)