mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:47 +00:00
[ruff
] Unnecessary rounding (RUF057
) (#14828)
Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
f8c9665742
commit
89ea0371a4
9 changed files with 660 additions and 67 deletions
59
crates/ruff_linter/resources/test/fixtures/ruff/RUF057.py
vendored
Normal file
59
crates/ruff_linter/resources/test/fixtures/ruff/RUF057.py
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
inferred_int = 1
|
||||||
|
inferred_float = 1.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
round(42) # Error (safe)
|
||||||
|
round(42, None) # Error (safe)
|
||||||
|
round(42, 2) # Error (safe)
|
||||||
|
round(42, inferred_int) # Error (safe)
|
||||||
|
round(42, 3 + 4) # Error (safe)
|
||||||
|
round(42, foo) # Error (unsafe)
|
||||||
|
|
||||||
|
|
||||||
|
round(42.) # No error
|
||||||
|
round(42., None) # No error
|
||||||
|
round(42., 2) # No error
|
||||||
|
round(42., inferred_int) # No error
|
||||||
|
round(42., 3 + 4) # No error
|
||||||
|
round(42., foo) # No error
|
||||||
|
|
||||||
|
|
||||||
|
round(4 + 2) # Error (safe)
|
||||||
|
round(4 + 2, None) # Error (safe)
|
||||||
|
round(4 + 2, 2) # Error (safe)
|
||||||
|
round(4 + 2, inferred_int) # Error (safe)
|
||||||
|
round(4 + 2, 3 + 4) # Error (safe)
|
||||||
|
round(4 + 2, foo) # Error (unsafe)
|
||||||
|
|
||||||
|
|
||||||
|
round(4. + 2.) # No error
|
||||||
|
round(4. + 2., None) # No error
|
||||||
|
round(4. + 2., 2) # No error
|
||||||
|
round(4. + 2., inferred_int) # No error
|
||||||
|
round(4. + 2., 3 + 4) # No error
|
||||||
|
round(4. + 2., foo) # No error
|
||||||
|
|
||||||
|
|
||||||
|
round(inferred_int) # Error (unsafe)
|
||||||
|
round(inferred_int, None) # Error (unsafe)
|
||||||
|
round(inferred_int, 2) # Error (unsafe)
|
||||||
|
round(inferred_int, inferred_int) # Error (unsafe)
|
||||||
|
round(inferred_int, 3 + 4) # Error (unsafe)
|
||||||
|
round(inferred_int, foo) # No error
|
||||||
|
|
||||||
|
|
||||||
|
round(inferred_float) # No error
|
||||||
|
round(inferred_float, None) # No error
|
||||||
|
round(inferred_float, 2) # No error
|
||||||
|
round(inferred_float, inferred_int) # No error
|
||||||
|
round(inferred_float, 3 + 4) # No error
|
||||||
|
round(inferred_float, foo) # No error
|
||||||
|
|
||||||
|
|
||||||
|
round(lorem) # No error
|
||||||
|
round(lorem, None) # No error
|
||||||
|
round(lorem, 2) # No error
|
||||||
|
round(lorem, inferred_int) # No error
|
||||||
|
round(lorem, 3 + 4) # No error
|
||||||
|
round(lorem, foo) # No error
|
|
@ -1114,6 +1114,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::FalsyDictGetFallback) {
|
if checker.enabled(Rule::FalsyDictGetFallback) {
|
||||||
ruff::rules::falsy_dict_get_fallback(checker, expr);
|
ruff::rules::falsy_dict_get_fallback(checker, expr);
|
||||||
}
|
}
|
||||||
|
if checker.enabled(Rule::UnnecessaryRound) {
|
||||||
|
ruff::rules::unnecessary_round(checker, call);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Expr::Dict(dict) => {
|
Expr::Dict(dict) => {
|
||||||
if checker.any_enabled(&[
|
if checker.any_enabled(&[
|
||||||
|
|
|
@ -992,6 +992,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Ruff, "052") => (RuleGroup::Preview, rules::ruff::rules::UsedDummyVariable),
|
(Ruff, "052") => (RuleGroup::Preview, rules::ruff::rules::UsedDummyVariable),
|
||||||
(Ruff, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression),
|
(Ruff, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression),
|
||||||
(Ruff, "056") => (RuleGroup::Preview, rules::ruff::rules::FalsyDictGetFallback),
|
(Ruff, "056") => (RuleGroup::Preview, rules::ruff::rules::FalsyDictGetFallback),
|
||||||
|
(Ruff, "057") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRound),
|
||||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||||
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
|
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
|
||||||
|
|
||||||
|
|
|
@ -419,6 +419,7 @@ mod tests {
|
||||||
#[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_1.py"))]
|
#[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_1.py"))]
|
||||||
#[test_case(Rule::UnnecessaryCastToInt, Path::new("RUF046.py"))]
|
#[test_case(Rule::UnnecessaryCastToInt, Path::new("RUF046.py"))]
|
||||||
#[test_case(Rule::PytestRaisesAmbiguousPattern, Path::new("RUF043.py"))]
|
#[test_case(Rule::PytestRaisesAmbiguousPattern, Path::new("RUF043.py"))]
|
||||||
|
#[test_case(Rule::UnnecessaryRound, Path::new("RUF057.py"))]
|
||||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!(
|
let snapshot = format!(
|
||||||
"preview__{}_{}",
|
"preview__{}_{}",
|
||||||
|
|
|
@ -38,6 +38,7 @@ pub(crate) use unnecessary_iterable_allocation_for_first_element::*;
|
||||||
pub(crate) use unnecessary_key_check::*;
|
pub(crate) use unnecessary_key_check::*;
|
||||||
pub(crate) use unnecessary_nested_literal::*;
|
pub(crate) use unnecessary_nested_literal::*;
|
||||||
pub(crate) use unnecessary_regular_expression::*;
|
pub(crate) use unnecessary_regular_expression::*;
|
||||||
|
pub(crate) use unnecessary_round::*;
|
||||||
pub(crate) use unraw_re_pattern::*;
|
pub(crate) use unraw_re_pattern::*;
|
||||||
pub(crate) use unsafe_markup_use::*;
|
pub(crate) use unsafe_markup_use::*;
|
||||||
pub(crate) use unused_async::*;
|
pub(crate) use unused_async::*;
|
||||||
|
@ -90,6 +91,7 @@ mod unnecessary_iterable_allocation_for_first_element;
|
||||||
mod unnecessary_key_check;
|
mod unnecessary_key_check;
|
||||||
mod unnecessary_nested_literal;
|
mod unnecessary_nested_literal;
|
||||||
mod unnecessary_regular_expression;
|
mod unnecessary_regular_expression;
|
||||||
|
mod unnecessary_round;
|
||||||
mod unraw_re_pattern;
|
mod unraw_re_pattern;
|
||||||
mod unsafe_markup_use;
|
mod unsafe_markup_use;
|
||||||
mod unused_async;
|
mod unused_async;
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::rules::ruff::rules::unnecessary_round::{
|
||||||
|
rounded_and_ndigits, InferredType, NdigitsValue, RoundedValue,
|
||||||
|
};
|
||||||
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||||
use ruff_python_ast::{Arguments, Expr, ExprCall, ExprNumberLiteral, Number};
|
use ruff_python_ast::{Arguments, Expr, ExprCall};
|
||||||
use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType};
|
use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType};
|
||||||
use ruff_python_semantic::analyze::typing;
|
|
||||||
use ruff_python_semantic::SemanticModel;
|
use ruff_python_semantic::SemanticModel;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
|
@ -76,12 +78,13 @@ pub(crate) fn unnecessary_cast_to_int(checker: &mut Checker, call: &ExprCall) {
|
||||||
|
|
||||||
let fix = unwrap_int_expression(checker, call, argument, applicability);
|
let fix = unwrap_int_expression(checker, call, argument, applicability);
|
||||||
let diagnostic = Diagnostic::new(UnnecessaryCastToInt, call.range);
|
let diagnostic = Diagnostic::new(UnnecessaryCastToInt, call.range);
|
||||||
|
|
||||||
checker.diagnostics.push(diagnostic.with_fix(fix));
|
checker.diagnostics.push(diagnostic.with_fix(fix));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a fix that replaces `int(expression)` with `expression`.
|
/// Creates a fix that replaces `int(expression)` with `expression`.
|
||||||
fn unwrap_int_expression(
|
fn unwrap_int_expression(
|
||||||
checker: &mut Checker,
|
checker: &Checker,
|
||||||
call: &ExprCall,
|
call: &ExprCall,
|
||||||
argument: &Expr,
|
argument: &Expr,
|
||||||
applicability: Applicability,
|
applicability: Applicability,
|
||||||
|
@ -148,78 +151,56 @@ fn single_argument_to_int_call<'a>(
|
||||||
Some(argument)
|
Some(argument)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of the first argument to `round()`
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
enum Rounded {
|
|
||||||
InferredInt,
|
|
||||||
InferredFloat,
|
|
||||||
LiteralInt,
|
|
||||||
LiteralFloat,
|
|
||||||
Other,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The type of the second argument to `round()`
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
enum Ndigits {
|
|
||||||
NotGiven,
|
|
||||||
LiteralInt,
|
|
||||||
LiteralNone,
|
|
||||||
Other,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the [`Applicability`] for a `round(..)` call.
|
/// Determines the [`Applicability`] for a `round(..)` call.
|
||||||
///
|
///
|
||||||
/// The Applicability depends on the `ndigits` and the number argument.
|
/// The Applicability depends on the `ndigits` and the number argument.
|
||||||
fn round_applicability(checker: &Checker, arguments: &Arguments) -> Option<Applicability> {
|
fn round_applicability(checker: &Checker, arguments: &Arguments) -> Option<Applicability> {
|
||||||
if arguments.len() > 2 {
|
let (_rounded, rounded_value, ndigits_value) = rounded_and_ndigits(checker, arguments)?;
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let number = arguments.find_argument_value("number", 0)?;
|
match (rounded_value, ndigits_value) {
|
||||||
let ndigits = arguments.find_argument_value("ndigits", 1);
|
// ```python
|
||||||
|
// int(round(2, 0))
|
||||||
|
// int(round(2))
|
||||||
|
// int(round(2, None))
|
||||||
|
// ```
|
||||||
|
(
|
||||||
|
RoundedValue::Int(InferredType::Equivalent),
|
||||||
|
NdigitsValue::Int(InferredType::Equivalent)
|
||||||
|
| NdigitsValue::NotGiven
|
||||||
|
| NdigitsValue::LiteralNone,
|
||||||
|
) => Some(Applicability::Safe),
|
||||||
|
|
||||||
let number_kind = match number {
|
// ```python
|
||||||
Expr::Name(name) => {
|
// int(round(2.0))
|
||||||
let semantic = checker.semantic();
|
// int(round(2.0, None))
|
||||||
|
// ```
|
||||||
|
(
|
||||||
|
RoundedValue::Float(InferredType::Equivalent),
|
||||||
|
NdigitsValue::NotGiven | NdigitsValue::LiteralNone,
|
||||||
|
) => Some(Applicability::Safe),
|
||||||
|
|
||||||
match semantic.only_binding(name).map(|id| semantic.binding(id)) {
|
// ```python
|
||||||
Some(binding) if typing::is_int(binding, semantic) => Rounded::InferredInt,
|
// a: int = 2 # or True
|
||||||
Some(binding) if typing::is_float(binding, semantic) => Rounded::InferredFloat,
|
// int(round(a, 1))
|
||||||
_ => Rounded::Other,
|
// int(round(a))
|
||||||
}
|
// int(round(a, None))
|
||||||
}
|
// ```
|
||||||
|
(
|
||||||
|
RoundedValue::Int(InferredType::AssignableTo),
|
||||||
|
NdigitsValue::Int(InferredType::Equivalent)
|
||||||
|
| NdigitsValue::NotGiven
|
||||||
|
| NdigitsValue::LiteralNone,
|
||||||
|
) => Some(Applicability::Unsafe),
|
||||||
|
|
||||||
Expr::NumberLiteral(ExprNumberLiteral { value, .. }) => match value {
|
// ```python
|
||||||
Number::Int(..) => Rounded::LiteralInt,
|
// int(round(2.0))
|
||||||
Number::Float(..) => Rounded::LiteralFloat,
|
// int(round(2.0, None))
|
||||||
Number::Complex { .. } => Rounded::Other,
|
// int(round(x))
|
||||||
},
|
// int(round(x, None))
|
||||||
|
// ```
|
||||||
_ => Rounded::Other,
|
(
|
||||||
};
|
RoundedValue::Float(InferredType::AssignableTo) | RoundedValue::Other,
|
||||||
|
NdigitsValue::NotGiven | NdigitsValue::LiteralNone,
|
||||||
let ndigits_kind = match ndigits {
|
|
||||||
None => Ndigits::NotGiven,
|
|
||||||
Some(Expr::NoneLiteral(_)) => Ndigits::LiteralNone,
|
|
||||||
|
|
||||||
Some(Expr::NumberLiteral(ExprNumberLiteral {
|
|
||||||
value: Number::Int(..),
|
|
||||||
..
|
|
||||||
})) => Ndigits::LiteralInt,
|
|
||||||
|
|
||||||
_ => Ndigits::Other,
|
|
||||||
};
|
|
||||||
|
|
||||||
match (number_kind, ndigits_kind) {
|
|
||||||
(Rounded::LiteralInt, Ndigits::LiteralInt)
|
|
||||||
| (Rounded::LiteralInt | Rounded::LiteralFloat, Ndigits::NotGiven | Ndigits::LiteralNone) => {
|
|
||||||
Some(Applicability::Safe)
|
|
||||||
}
|
|
||||||
|
|
||||||
(Rounded::InferredInt, Ndigits::LiteralInt)
|
|
||||||
| (
|
|
||||||
Rounded::InferredInt | Rounded::InferredFloat | Rounded::Other,
|
|
||||||
Ndigits::NotGiven | Ndigits::LiteralNone,
|
|
||||||
) => Some(Applicability::Unsafe),
|
) => Some(Applicability::Unsafe),
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
201
crates/ruff_linter/src/rules/ruff/rules/unnecessary_round.rs
Normal file
201
crates/ruff_linter/src/rules/ruff/rules/unnecessary_round.rs
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix};
|
||||||
|
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||||
|
use ruff_python_ast::{Arguments, Expr, ExprCall};
|
||||||
|
use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType};
|
||||||
|
use ruff_python_semantic::analyze::typing;
|
||||||
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for `round()` calls that have no effect on the input.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Rounding a value that's already an integer is unnecessary.
|
||||||
|
/// It's clearer to use the value directly.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```python
|
||||||
|
/// a = round(1, 0)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
///
|
||||||
|
/// ```python
|
||||||
|
/// a = 1
|
||||||
|
/// ```
|
||||||
|
#[derive(ViolationMetadata)]
|
||||||
|
pub(crate) struct UnnecessaryRound;
|
||||||
|
|
||||||
|
impl AlwaysFixableViolation for UnnecessaryRound {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
"Value being rounded is already an integer".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fix_title(&self) -> String {
|
||||||
|
"Remove unnecessary `round` call".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RUF057
|
||||||
|
pub(crate) fn unnecessary_round(checker: &mut Checker, call: &ExprCall) {
|
||||||
|
let arguments = &call.arguments;
|
||||||
|
|
||||||
|
if !checker.semantic().match_builtin_expr(&call.func, "round") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((rounded, rounded_value, ndigits_value)) = rounded_and_ndigits(checker, arguments)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let applicability = match (rounded_value, ndigits_value) {
|
||||||
|
// ```python
|
||||||
|
// rounded(1, unknown)
|
||||||
|
// ```
|
||||||
|
(RoundedValue::Int(InferredType::Equivalent), NdigitsValue::Other) => Applicability::Unsafe,
|
||||||
|
|
||||||
|
(_, NdigitsValue::Other) => return,
|
||||||
|
|
||||||
|
// ```python
|
||||||
|
// some_int: int
|
||||||
|
//
|
||||||
|
// rounded(1)
|
||||||
|
// rounded(1, None)
|
||||||
|
// rounded(1, 42)
|
||||||
|
// rounded(1, 4 + 2)
|
||||||
|
// rounded(1, some_int)
|
||||||
|
// ```
|
||||||
|
(RoundedValue::Int(InferredType::Equivalent), _) => Applicability::Safe,
|
||||||
|
|
||||||
|
// ```python
|
||||||
|
// some_int: int
|
||||||
|
// some_other_int: int
|
||||||
|
//
|
||||||
|
// rounded(some_int)
|
||||||
|
// rounded(some_int, None)
|
||||||
|
// rounded(some_int, 42)
|
||||||
|
// rounded(some_int, 4 + 2)
|
||||||
|
// rounded(some_int, some_other_int)
|
||||||
|
// ```
|
||||||
|
(RoundedValue::Int(InferredType::AssignableTo), _) => Applicability::Unsafe,
|
||||||
|
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let edit = unwrap_round_call(checker, call, rounded);
|
||||||
|
let fix = Fix::applicable_edit(edit, applicability);
|
||||||
|
|
||||||
|
let diagnostic = Diagnostic::new(UnnecessaryRound, call.range);
|
||||||
|
|
||||||
|
checker.diagnostics.push(diagnostic.with_fix(fix));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(super) enum InferredType {
|
||||||
|
/// The value is an exact instance of the type in question.
|
||||||
|
Equivalent,
|
||||||
|
/// The value is an instance of the type in question or a subtype thereof.
|
||||||
|
AssignableTo,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type of the first argument to `round()`
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(super) enum RoundedValue {
|
||||||
|
Int(InferredType),
|
||||||
|
Float(InferredType),
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type of the second argument to `round()`
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(super) enum NdigitsValue {
|
||||||
|
NotGiven,
|
||||||
|
LiteralNone,
|
||||||
|
Int(InferredType),
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the rounded and `ndigits` values from `arguments`.
|
||||||
|
///
|
||||||
|
/// Returns a tripled where the first element is the rounded value's expression, the second is the rounded value,
|
||||||
|
///and the third is the `ndigits` value.
|
||||||
|
pub(super) fn rounded_and_ndigits<'a>(
|
||||||
|
checker: &Checker,
|
||||||
|
arguments: &'a Arguments,
|
||||||
|
) -> Option<(&'a Expr, RoundedValue, NdigitsValue)> {
|
||||||
|
if arguments.len() > 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rounded = arguments.find_argument_value("number", 0)?;
|
||||||
|
let ndigits = arguments.find_argument_value("ndigits", 1);
|
||||||
|
|
||||||
|
let rounded_kind = match rounded {
|
||||||
|
Expr::Name(name) => {
|
||||||
|
let semantic = checker.semantic();
|
||||||
|
|
||||||
|
match semantic.only_binding(name).map(|id| semantic.binding(id)) {
|
||||||
|
Some(binding) if typing::is_int(binding, semantic) => {
|
||||||
|
RoundedValue::Int(InferredType::AssignableTo)
|
||||||
|
}
|
||||||
|
Some(binding) if typing::is_float(binding, semantic) => {
|
||||||
|
RoundedValue::Float(InferredType::AssignableTo)
|
||||||
|
}
|
||||||
|
_ => RoundedValue::Other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => match ResolvedPythonType::from(rounded) {
|
||||||
|
ResolvedPythonType::Atom(PythonType::Number(NumberLike::Integer)) => {
|
||||||
|
RoundedValue::Int(InferredType::Equivalent)
|
||||||
|
}
|
||||||
|
ResolvedPythonType::Atom(PythonType::Number(NumberLike::Float)) => {
|
||||||
|
RoundedValue::Float(InferredType::Equivalent)
|
||||||
|
}
|
||||||
|
_ => RoundedValue::Other,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let ndigits_kind = match ndigits {
|
||||||
|
None => NdigitsValue::NotGiven,
|
||||||
|
Some(Expr::NoneLiteral(_)) => NdigitsValue::LiteralNone,
|
||||||
|
|
||||||
|
Some(Expr::Name(name)) => {
|
||||||
|
let semantic = checker.semantic();
|
||||||
|
|
||||||
|
match semantic.only_binding(name).map(|id| semantic.binding(id)) {
|
||||||
|
Some(binding) if typing::is_int(binding, semantic) => {
|
||||||
|
NdigitsValue::Int(InferredType::AssignableTo)
|
||||||
|
}
|
||||||
|
_ => NdigitsValue::Other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ndigits) => match ResolvedPythonType::from(ndigits) {
|
||||||
|
ResolvedPythonType::Atom(PythonType::Number(NumberLike::Integer)) => {
|
||||||
|
NdigitsValue::Int(InferredType::Equivalent)
|
||||||
|
}
|
||||||
|
_ => NdigitsValue::Other,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((rounded, rounded_kind, ndigits_kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_round_call(checker: &Checker, call: &ExprCall, rounded: &Expr) -> Edit {
|
||||||
|
let (locator, semantic) = (checker.locator(), checker.semantic());
|
||||||
|
|
||||||
|
let rounded_expr = locator.slice(rounded.range());
|
||||||
|
|
||||||
|
let has_parent_expr = semantic.current_expression_parent().is_some();
|
||||||
|
let new_content = if has_parent_expr || rounded.is_named_expr() {
|
||||||
|
format!("({rounded_expr})")
|
||||||
|
} else {
|
||||||
|
rounded_expr.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
Edit::range_replacement(new_content, call.range)
|
||||||
|
}
|
|
@ -0,0 +1,344 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
RUF057.py:6:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
6 | round(42) # Error (safe)
|
||||||
|
| ^^^^^^^^^ RUF057
|
||||||
|
7 | round(42, None) # Error (safe)
|
||||||
|
8 | round(42, 2) # Error (safe)
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
3 3 |
|
||||||
|
4 4 |
|
||||||
|
5 5 |
|
||||||
|
6 |-round(42) # Error (safe)
|
||||||
|
6 |+42 # Error (safe)
|
||||||
|
7 7 | round(42, None) # Error (safe)
|
||||||
|
8 8 | round(42, 2) # Error (safe)
|
||||||
|
9 9 | round(42, inferred_int) # Error (safe)
|
||||||
|
|
||||||
|
RUF057.py:7:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
6 | round(42) # Error (safe)
|
||||||
|
7 | round(42, None) # Error (safe)
|
||||||
|
| ^^^^^^^^^^^^^^^ RUF057
|
||||||
|
8 | round(42, 2) # Error (safe)
|
||||||
|
9 | round(42, inferred_int) # Error (safe)
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
4 4 |
|
||||||
|
5 5 |
|
||||||
|
6 6 | round(42) # Error (safe)
|
||||||
|
7 |-round(42, None) # Error (safe)
|
||||||
|
7 |+42 # Error (safe)
|
||||||
|
8 8 | round(42, 2) # Error (safe)
|
||||||
|
9 9 | round(42, inferred_int) # Error (safe)
|
||||||
|
10 10 | round(42, 3 + 4) # Error (safe)
|
||||||
|
|
||||||
|
RUF057.py:8:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
6 | round(42) # Error (safe)
|
||||||
|
7 | round(42, None) # Error (safe)
|
||||||
|
8 | round(42, 2) # Error (safe)
|
||||||
|
| ^^^^^^^^^^^^ RUF057
|
||||||
|
9 | round(42, inferred_int) # Error (safe)
|
||||||
|
10 | round(42, 3 + 4) # Error (safe)
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
5 5 |
|
||||||
|
6 6 | round(42) # Error (safe)
|
||||||
|
7 7 | round(42, None) # Error (safe)
|
||||||
|
8 |-round(42, 2) # Error (safe)
|
||||||
|
8 |+42 # Error (safe)
|
||||||
|
9 9 | round(42, inferred_int) # Error (safe)
|
||||||
|
10 10 | round(42, 3 + 4) # Error (safe)
|
||||||
|
11 11 | round(42, foo) # Error (unsafe)
|
||||||
|
|
||||||
|
RUF057.py:9:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
7 | round(42, None) # Error (safe)
|
||||||
|
8 | round(42, 2) # Error (safe)
|
||||||
|
9 | round(42, inferred_int) # Error (safe)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^ RUF057
|
||||||
|
10 | round(42, 3 + 4) # Error (safe)
|
||||||
|
11 | round(42, foo) # Error (unsafe)
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
6 6 | round(42) # Error (safe)
|
||||||
|
7 7 | round(42, None) # Error (safe)
|
||||||
|
8 8 | round(42, 2) # Error (safe)
|
||||||
|
9 |-round(42, inferred_int) # Error (safe)
|
||||||
|
9 |+42 # Error (safe)
|
||||||
|
10 10 | round(42, 3 + 4) # Error (safe)
|
||||||
|
11 11 | round(42, foo) # Error (unsafe)
|
||||||
|
12 12 |
|
||||||
|
|
||||||
|
RUF057.py:10:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
8 | round(42, 2) # Error (safe)
|
||||||
|
9 | round(42, inferred_int) # Error (safe)
|
||||||
|
10 | round(42, 3 + 4) # Error (safe)
|
||||||
|
| ^^^^^^^^^^^^^^^^ RUF057
|
||||||
|
11 | round(42, foo) # Error (unsafe)
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
7 7 | round(42, None) # Error (safe)
|
||||||
|
8 8 | round(42, 2) # Error (safe)
|
||||||
|
9 9 | round(42, inferred_int) # Error (safe)
|
||||||
|
10 |-round(42, 3 + 4) # Error (safe)
|
||||||
|
10 |+42 # Error (safe)
|
||||||
|
11 11 | round(42, foo) # Error (unsafe)
|
||||||
|
12 12 |
|
||||||
|
13 13 |
|
||||||
|
|
||||||
|
RUF057.py:11:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
9 | round(42, inferred_int) # Error (safe)
|
||||||
|
10 | round(42, 3 + 4) # Error (safe)
|
||||||
|
11 | round(42, foo) # Error (unsafe)
|
||||||
|
| ^^^^^^^^^^^^^^ RUF057
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
8 8 | round(42, 2) # Error (safe)
|
||||||
|
9 9 | round(42, inferred_int) # Error (safe)
|
||||||
|
10 10 | round(42, 3 + 4) # Error (safe)
|
||||||
|
11 |-round(42, foo) # Error (unsafe)
|
||||||
|
11 |+42 # Error (unsafe)
|
||||||
|
12 12 |
|
||||||
|
13 13 |
|
||||||
|
14 14 | round(42.) # No error
|
||||||
|
|
||||||
|
RUF057.py:22:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
22 | round(4 + 2) # Error (safe)
|
||||||
|
| ^^^^^^^^^^^^ RUF057
|
||||||
|
23 | round(4 + 2, None) # Error (safe)
|
||||||
|
24 | round(4 + 2, 2) # Error (safe)
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
19 19 | round(42., foo) # No error
|
||||||
|
20 20 |
|
||||||
|
21 21 |
|
||||||
|
22 |-round(4 + 2) # Error (safe)
|
||||||
|
22 |+4 + 2 # Error (safe)
|
||||||
|
23 23 | round(4 + 2, None) # Error (safe)
|
||||||
|
24 24 | round(4 + 2, 2) # Error (safe)
|
||||||
|
25 25 | round(4 + 2, inferred_int) # Error (safe)
|
||||||
|
|
||||||
|
RUF057.py:23:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
22 | round(4 + 2) # Error (safe)
|
||||||
|
23 | round(4 + 2, None) # Error (safe)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^ RUF057
|
||||||
|
24 | round(4 + 2, 2) # Error (safe)
|
||||||
|
25 | round(4 + 2, inferred_int) # Error (safe)
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
20 20 |
|
||||||
|
21 21 |
|
||||||
|
22 22 | round(4 + 2) # Error (safe)
|
||||||
|
23 |-round(4 + 2, None) # Error (safe)
|
||||||
|
23 |+4 + 2 # Error (safe)
|
||||||
|
24 24 | round(4 + 2, 2) # Error (safe)
|
||||||
|
25 25 | round(4 + 2, inferred_int) # Error (safe)
|
||||||
|
26 26 | round(4 + 2, 3 + 4) # Error (safe)
|
||||||
|
|
||||||
|
RUF057.py:24:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
22 | round(4 + 2) # Error (safe)
|
||||||
|
23 | round(4 + 2, None) # Error (safe)
|
||||||
|
24 | round(4 + 2, 2) # Error (safe)
|
||||||
|
| ^^^^^^^^^^^^^^^ RUF057
|
||||||
|
25 | round(4 + 2, inferred_int) # Error (safe)
|
||||||
|
26 | round(4 + 2, 3 + 4) # Error (safe)
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
21 21 |
|
||||||
|
22 22 | round(4 + 2) # Error (safe)
|
||||||
|
23 23 | round(4 + 2, None) # Error (safe)
|
||||||
|
24 |-round(4 + 2, 2) # Error (safe)
|
||||||
|
24 |+4 + 2 # Error (safe)
|
||||||
|
25 25 | round(4 + 2, inferred_int) # Error (safe)
|
||||||
|
26 26 | round(4 + 2, 3 + 4) # Error (safe)
|
||||||
|
27 27 | round(4 + 2, foo) # Error (unsafe)
|
||||||
|
|
||||||
|
RUF057.py:25:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
23 | round(4 + 2, None) # Error (safe)
|
||||||
|
24 | round(4 + 2, 2) # Error (safe)
|
||||||
|
25 | round(4 + 2, inferred_int) # Error (safe)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF057
|
||||||
|
26 | round(4 + 2, 3 + 4) # Error (safe)
|
||||||
|
27 | round(4 + 2, foo) # Error (unsafe)
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
22 22 | round(4 + 2) # Error (safe)
|
||||||
|
23 23 | round(4 + 2, None) # Error (safe)
|
||||||
|
24 24 | round(4 + 2, 2) # Error (safe)
|
||||||
|
25 |-round(4 + 2, inferred_int) # Error (safe)
|
||||||
|
25 |+4 + 2 # Error (safe)
|
||||||
|
26 26 | round(4 + 2, 3 + 4) # Error (safe)
|
||||||
|
27 27 | round(4 + 2, foo) # Error (unsafe)
|
||||||
|
28 28 |
|
||||||
|
|
||||||
|
RUF057.py:26:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
24 | round(4 + 2, 2) # Error (safe)
|
||||||
|
25 | round(4 + 2, inferred_int) # Error (safe)
|
||||||
|
26 | round(4 + 2, 3 + 4) # Error (safe)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ RUF057
|
||||||
|
27 | round(4 + 2, foo) # Error (unsafe)
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
23 23 | round(4 + 2, None) # Error (safe)
|
||||||
|
24 24 | round(4 + 2, 2) # Error (safe)
|
||||||
|
25 25 | round(4 + 2, inferred_int) # Error (safe)
|
||||||
|
26 |-round(4 + 2, 3 + 4) # Error (safe)
|
||||||
|
26 |+4 + 2 # Error (safe)
|
||||||
|
27 27 | round(4 + 2, foo) # Error (unsafe)
|
||||||
|
28 28 |
|
||||||
|
29 29 |
|
||||||
|
|
||||||
|
RUF057.py:27:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
25 | round(4 + 2, inferred_int) # Error (safe)
|
||||||
|
26 | round(4 + 2, 3 + 4) # Error (safe)
|
||||||
|
27 | round(4 + 2, foo) # Error (unsafe)
|
||||||
|
| ^^^^^^^^^^^^^^^^^ RUF057
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
24 24 | round(4 + 2, 2) # Error (safe)
|
||||||
|
25 25 | round(4 + 2, inferred_int) # Error (safe)
|
||||||
|
26 26 | round(4 + 2, 3 + 4) # Error (safe)
|
||||||
|
27 |-round(4 + 2, foo) # Error (unsafe)
|
||||||
|
27 |+4 + 2 # Error (unsafe)
|
||||||
|
28 28 |
|
||||||
|
29 29 |
|
||||||
|
30 30 | round(4. + 2.) # No error
|
||||||
|
|
||||||
|
RUF057.py:38:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
38 | round(inferred_int) # Error (unsafe)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ RUF057
|
||||||
|
39 | round(inferred_int, None) # Error (unsafe)
|
||||||
|
40 | round(inferred_int, 2) # Error (unsafe)
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
35 35 | round(4. + 2., foo) # No error
|
||||||
|
36 36 |
|
||||||
|
37 37 |
|
||||||
|
38 |-round(inferred_int) # Error (unsafe)
|
||||||
|
38 |+inferred_int # Error (unsafe)
|
||||||
|
39 39 | round(inferred_int, None) # Error (unsafe)
|
||||||
|
40 40 | round(inferred_int, 2) # Error (unsafe)
|
||||||
|
41 41 | round(inferred_int, inferred_int) # Error (unsafe)
|
||||||
|
|
||||||
|
RUF057.py:39:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
38 | round(inferred_int) # Error (unsafe)
|
||||||
|
39 | round(inferred_int, None) # Error (unsafe)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF057
|
||||||
|
40 | round(inferred_int, 2) # Error (unsafe)
|
||||||
|
41 | round(inferred_int, inferred_int) # Error (unsafe)
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
36 36 |
|
||||||
|
37 37 |
|
||||||
|
38 38 | round(inferred_int) # Error (unsafe)
|
||||||
|
39 |-round(inferred_int, None) # Error (unsafe)
|
||||||
|
39 |+inferred_int # Error (unsafe)
|
||||||
|
40 40 | round(inferred_int, 2) # Error (unsafe)
|
||||||
|
41 41 | round(inferred_int, inferred_int) # Error (unsafe)
|
||||||
|
42 42 | round(inferred_int, 3 + 4) # Error (unsafe)
|
||||||
|
|
||||||
|
RUF057.py:40:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
38 | round(inferred_int) # Error (unsafe)
|
||||||
|
39 | round(inferred_int, None) # Error (unsafe)
|
||||||
|
40 | round(inferred_int, 2) # Error (unsafe)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^ RUF057
|
||||||
|
41 | round(inferred_int, inferred_int) # Error (unsafe)
|
||||||
|
42 | round(inferred_int, 3 + 4) # Error (unsafe)
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
37 37 |
|
||||||
|
38 38 | round(inferred_int) # Error (unsafe)
|
||||||
|
39 39 | round(inferred_int, None) # Error (unsafe)
|
||||||
|
40 |-round(inferred_int, 2) # Error (unsafe)
|
||||||
|
40 |+inferred_int # Error (unsafe)
|
||||||
|
41 41 | round(inferred_int, inferred_int) # Error (unsafe)
|
||||||
|
42 42 | round(inferred_int, 3 + 4) # Error (unsafe)
|
||||||
|
43 43 | round(inferred_int, foo) # No error
|
||||||
|
|
||||||
|
RUF057.py:41:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
39 | round(inferred_int, None) # Error (unsafe)
|
||||||
|
40 | round(inferred_int, 2) # Error (unsafe)
|
||||||
|
41 | round(inferred_int, inferred_int) # Error (unsafe)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF057
|
||||||
|
42 | round(inferred_int, 3 + 4) # Error (unsafe)
|
||||||
|
43 | round(inferred_int, foo) # No error
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
38 38 | round(inferred_int) # Error (unsafe)
|
||||||
|
39 39 | round(inferred_int, None) # Error (unsafe)
|
||||||
|
40 40 | round(inferred_int, 2) # Error (unsafe)
|
||||||
|
41 |-round(inferred_int, inferred_int) # Error (unsafe)
|
||||||
|
41 |+inferred_int # Error (unsafe)
|
||||||
|
42 42 | round(inferred_int, 3 + 4) # Error (unsafe)
|
||||||
|
43 43 | round(inferred_int, foo) # No error
|
||||||
|
44 44 |
|
||||||
|
|
||||||
|
RUF057.py:42:1: RUF057 [*] Value being rounded is already an integer
|
||||||
|
|
|
||||||
|
40 | round(inferred_int, 2) # Error (unsafe)
|
||||||
|
41 | round(inferred_int, inferred_int) # Error (unsafe)
|
||||||
|
42 | round(inferred_int, 3 + 4) # Error (unsafe)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF057
|
||||||
|
43 | round(inferred_int, foo) # No error
|
||||||
|
|
|
||||||
|
= help: Remove unnecessary `round` call
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
39 39 | round(inferred_int, None) # Error (unsafe)
|
||||||
|
40 40 | round(inferred_int, 2) # Error (unsafe)
|
||||||
|
41 41 | round(inferred_int, inferred_int) # Error (unsafe)
|
||||||
|
42 |-round(inferred_int, 3 + 4) # Error (unsafe)
|
||||||
|
42 |+inferred_int # Error (unsafe)
|
||||||
|
43 43 | round(inferred_int, foo) # No error
|
||||||
|
44 44 |
|
||||||
|
45 45 |
|
1
ruff.schema.json
generated
1
ruff.schema.json
generated
|
@ -3873,6 +3873,7 @@
|
||||||
"RUF052",
|
"RUF052",
|
||||||
"RUF055",
|
"RUF055",
|
||||||
"RUF056",
|
"RUF056",
|
||||||
|
"RUF057",
|
||||||
"RUF1",
|
"RUF1",
|
||||||
"RUF10",
|
"RUF10",
|
||||||
"RUF100",
|
"RUF100",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue