diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF057.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF057.py new file mode 100644 index 0000000000000..df33b0fb62944 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF057.py @@ -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 diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index d3e55f12a7475..6d00aad227d6e 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -1114,6 +1114,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::FalsyDictGetFallback) { ruff::rules::falsy_dict_get_fallback(checker, expr); } + if checker.enabled(Rule::UnnecessaryRound) { + ruff::rules::unnecessary_round(checker, call); + } } Expr::Dict(dict) => { if checker.any_enabled(&[ diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index a90a87c63007a..e2e9d67bbd8ed 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -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, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression), (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, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA), diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 280eb3f167f76..7f67af7c1d866 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -419,6 +419,7 @@ mod tests { #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_1.py"))] #[test_case(Rule::UnnecessaryCastToInt, Path::new("RUF046.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<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/ruff/rules/mod.rs b/crates/ruff_linter/src/rules/ruff/rules/mod.rs index c612f8615015e..241d8a2bf6c83 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mod.rs @@ -38,6 +38,7 @@ pub(crate) use unnecessary_iterable_allocation_for_first_element::*; pub(crate) use unnecessary_key_check::*; pub(crate) use unnecessary_nested_literal::*; pub(crate) use unnecessary_regular_expression::*; +pub(crate) use unnecessary_round::*; pub(crate) use unraw_re_pattern::*; pub(crate) use unsafe_markup_use::*; pub(crate) use unused_async::*; @@ -90,6 +91,7 @@ mod unnecessary_iterable_allocation_for_first_element; mod unnecessary_key_check; mod unnecessary_nested_literal; mod unnecessary_regular_expression; +mod unnecessary_round; mod unraw_re_pattern; mod unsafe_markup_use; mod unused_async; diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_cast_to_int.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_cast_to_int.rs index 7979bb14e737d..d4d29388907df 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_cast_to_int.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_cast_to_int.rs @@ -1,9 +1,11 @@ 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_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::typing; use ruff_python_semantic::SemanticModel; 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 diagnostic = Diagnostic::new(UnnecessaryCastToInt, call.range); + checker.diagnostics.push(diagnostic.with_fix(fix)); } /// Creates a fix that replaces `int(expression)` with `expression`. fn unwrap_int_expression( - checker: &mut Checker, + checker: &Checker, call: &ExprCall, argument: &Expr, applicability: Applicability, @@ -148,78 +151,56 @@ fn single_argument_to_int_call<'a>( 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. /// /// The Applicability depends on the `ndigits` and the number argument. fn round_applicability(checker: &Checker, arguments: &Arguments) -> Option { - if arguments.len() > 2 { - return None; - } - - let number = arguments.find_argument_value("number", 0)?; - let ndigits = arguments.find_argument_value("ndigits", 1); - - let number_kind = match number { - 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) => Rounded::InferredInt, - Some(binding) if typing::is_float(binding, semantic) => Rounded::InferredFloat, - _ => Rounded::Other, - } - } - - Expr::NumberLiteral(ExprNumberLiteral { value, .. }) => match value { - Number::Int(..) => Rounded::LiteralInt, - Number::Float(..) => Rounded::LiteralFloat, - Number::Complex { .. } => Rounded::Other, - }, - - _ => Rounded::Other, - }; - - 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) - } + let (_rounded, rounded_value, ndigits_value) = rounded_and_ndigits(checker, arguments)?; + + match (rounded_value, ndigits_value) { + // ```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), + + // ```python + // int(round(2.0)) + // int(round(2.0, None)) + // ``` + ( + RoundedValue::Float(InferredType::Equivalent), + NdigitsValue::NotGiven | NdigitsValue::LiteralNone, + ) => Some(Applicability::Safe), + + // ```python + // a: int = 2 # or True + // int(round(a, 1)) + // int(round(a)) + // int(round(a, None)) + // ``` + ( + RoundedValue::Int(InferredType::AssignableTo), + NdigitsValue::Int(InferredType::Equivalent) + | NdigitsValue::NotGiven + | NdigitsValue::LiteralNone, + ) => Some(Applicability::Unsafe), - (Rounded::InferredInt, Ndigits::LiteralInt) - | ( - Rounded::InferredInt | Rounded::InferredFloat | Rounded::Other, - Ndigits::NotGiven | Ndigits::LiteralNone, + // ```python + // int(round(2.0)) + // int(round(2.0, None)) + // int(round(x)) + // int(round(x, None)) + // ``` + ( + RoundedValue::Float(InferredType::AssignableTo) | RoundedValue::Other, + NdigitsValue::NotGiven | NdigitsValue::LiteralNone, ) => Some(Applicability::Unsafe), _ => None, diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_round.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_round.rs new file mode 100644 index 0000000000000..4b2ee85b48688 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_round.rs @@ -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) +} diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF057_RUF057.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF057_RUF057.py.snap new file mode 100644 index 0000000000000..f44d92622db16 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF057_RUF057.py.snap @@ -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 | diff --git a/ruff.schema.json b/ruff.schema.json index e2460efdf3a33..ee7380fe88a68 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3873,6 +3873,7 @@ "RUF052", "RUF055", "RUF056", + "RUF057", "RUF1", "RUF10", "RUF100",