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 ddbe26a24ae47..787b253a6bfee 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,8 +1,7 @@ use crate::checkers::ast::Checker; -use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, ViolationMetadata}; -use ruff_python_ast::{Arguments, Expr, ExprCall, ExprName, ExprNumberLiteral, Number}; -use ruff_python_semantic::analyze::typing; +use ruff_python_ast::{Expr, ExprCall}; use ruff_python_semantic::SemanticModel; use ruff_text_size::TextRange; @@ -58,7 +57,7 @@ pub(crate) fn unnecessary_cast_to_int(checker: &mut Checker, call: &ExprCall) { return; }; - let (func, arguments) = (&inner_call.func, &inner_call.arguments); + let func = &inner_call.func; let (outer_range, inner_range) = (call.range, inner_call.range); let Some(qualified_name) = checker.semantic().resolve_qualified_name(func) else { @@ -72,26 +71,17 @@ pub(crate) fn unnecessary_cast_to_int(checker: &mut Checker, call: &ExprCall) { Fix::safe_edit(replace_with_inner(checker, outer_range, inner_range)) } - // Depends on `ndigits` and `number.__round__` - ["" | "builtins", "round"] => { - if let Some(fix) = replace_with_shortened_round_call(checker, outer_range, arguments) { - fix - } else { - return; - } - } - - // Depends on `__ceil__`/`__floor__`/`__trunc__` - ["math", "ceil" | "floor" | "trunc"] => { + // Depends on `__ceil__`/`__floor__`/`__trunc__`/`__round__` + ["math", "ceil" | "floor" | "trunc"] | ["" | "builtins", "round"] => { Fix::unsafe_edit(replace_with_inner(checker, outer_range, inner_range)) } _ => return, }; - checker - .diagnostics - .push(Diagnostic::new(UnnecessaryCastToInt, call.range).with_fix(fix)); + let diagnostic = Diagnostic::new(UnnecessaryCastToInt, call.range); + + checker.diagnostics.push(diagnostic.with_fix(fix)); } fn single_argument_to_int_call<'a>( @@ -117,65 +107,6 @@ fn single_argument_to_int_call<'a>( Some(argument) } -/// Returns an [`Edit`] when the call is of any of the forms: -/// * `round(integer)`, `round(integer, 0)`, `round(integer, None)` -/// * `round(whatever)`, `round(whatever, None)` -fn replace_with_shortened_round_call( - checker: &Checker, - outer_range: TextRange, - arguments: &Arguments, -) -> Option { - if arguments.len() > 2 { - return None; - } - - let number = arguments.find_argument("number", 0)?; - let ndigits = arguments.find_argument("ndigits", 1); - - let number_is_int = match number { - Expr::Name(name) => is_int(checker.semantic(), name), - Expr::NumberLiteral(ExprNumberLiteral { value, .. }) => matches!(value, Number::Int(..)), - _ => false, - }; - - match ndigits { - Some(Expr::NumberLiteral(ExprNumberLiteral { value, .. })) - if is_literal_zero(value) && number_is_int => {} - Some(Expr::NoneLiteral(_)) | None => {} - _ => return None, - }; - - let number_expr = checker.locator().slice(number); - let new_content = format!("round({number_expr})"); - - let applicability = if number_is_int { - Applicability::Safe - } else { - Applicability::Unsafe - }; - - Some(Fix::applicable_edit( - Edit::range_replacement(new_content, outer_range), - applicability, - )) -} - -fn is_int(semantic: &SemanticModel, name: &ExprName) -> bool { - let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else { - return false; - }; - - typing::is_int(binding, semantic) -} - -fn is_literal_zero(value: &Number) -> bool { - let Number::Int(int) = value else { - return false; - }; - - matches!(int.as_u8(), Some(0)) -} - fn replace_with_inner(checker: &Checker, outer_range: TextRange, inner_range: TextRange) -> Edit { let inner_expr = checker.locator().slice(inner_range); diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF046_RUF046.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF046_RUF046.py.snap index 22e139816bcf8..bc012fadd4c0c 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF046_RUF046.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF046_RUF046.py.snap @@ -296,7 +296,7 @@ RUF046.py:31:1: RUF046 [*] Value being casted is already an integer | = help: Remove unnecessary conversion to `int` -ℹ Safe fix +ℹ Unsafe fix 28 28 | ### `round()` 29 29 | 30 30 | ## Errors @@ -316,12 +316,12 @@ RUF046.py:32:1: RUF046 [*] Value being casted is already an integer | = help: Remove unnecessary conversion to `int` -ℹ Safe fix +ℹ Unsafe fix 29 29 | 30 30 | ## Errors 31 31 | int(round(0)) 32 |-int(round(0, 0)) - 32 |+round(0) + 32 |+round(0, 0) 33 33 | int(round(0, None)) 34 34 | 35 35 | int(round(0.1)) @@ -337,12 +337,12 @@ RUF046.py:33:1: RUF046 [*] Value being casted is already an integer | = help: Remove unnecessary conversion to `int` -ℹ Safe fix +ℹ Unsafe fix 30 30 | ## Errors 31 31 | int(round(0)) 32 32 | int(round(0, 0)) 33 |-int(round(0, None)) - 33 |+round(0) + 33 |+round(0, None) 34 34 | 35 35 | int(round(0.1)) 36 36 | int(round(0.1, None)) @@ -382,7 +382,7 @@ RUF046.py:36:1: RUF046 [*] Value being casted is already an integer 34 34 | 35 35 | int(round(0.1)) 36 |-int(round(0.1, None)) - 36 |+round(0.1) + 36 |+round(0.1, None) 37 37 | 38 38 | # Argument type is not checked 39 39 | foo = type("Foo", (), {"__round__": lambda self: 4.2})() @@ -408,6 +408,25 @@ RUF046.py:41:1: RUF046 [*] Value being casted is already an integer 43 43 | int(round(foo, None)) 44 44 | +RUF046.py:42:1: RUF046 [*] Value being casted is already an integer + | +41 | int(round(foo)) +42 | int(round(foo, 0)) + | ^^^^^^^^^^^^^^^^^^ RUF046 +43 | int(round(foo, None)) + | + = help: Remove unnecessary conversion to `int` + +ℹ Unsafe fix +39 39 | foo = type("Foo", (), {"__round__": lambda self: 4.2})() +40 40 | +41 41 | int(round(foo)) +42 |-int(round(foo, 0)) + 42 |+round(foo, 0) +43 43 | int(round(foo, None)) +44 44 | +45 45 | ## No errors + RUF046.py:43:1: RUF046 [*] Value being casted is already an integer | 41 | int(round(foo)) @@ -424,7 +443,82 @@ RUF046.py:43:1: RUF046 [*] Value being casted is already an integer 41 41 | int(round(foo)) 42 42 | int(round(foo, 0)) 43 |-int(round(foo, None)) - 43 |+round(foo) + 43 |+round(foo, None) 44 44 | 45 45 | ## No errors 46 46 | int(round(0, 3.14)) + +RUF046.py:46:1: RUF046 [*] Value being casted is already an integer + | +45 | ## No errors +46 | int(round(0, 3.14)) + | ^^^^^^^^^^^^^^^^^^^ RUF046 +47 | int(round(0, non_literal)) +48 | int(round(0, 0), base) + | + = help: Remove unnecessary conversion to `int` + +ℹ Unsafe fix +43 43 | int(round(foo, None)) +44 44 | +45 45 | ## No errors +46 |-int(round(0, 3.14)) + 46 |+round(0, 3.14) +47 47 | int(round(0, non_literal)) +48 48 | int(round(0, 0), base) +49 49 | int(round(0, 0, extra=keyword)) + +RUF046.py:47:1: RUF046 [*] Value being casted is already an integer + | +45 | ## No errors +46 | int(round(0, 3.14)) +47 | int(round(0, non_literal)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF046 +48 | int(round(0, 0), base) +49 | int(round(0, 0, extra=keyword)) + | + = help: Remove unnecessary conversion to `int` + +ℹ Unsafe fix +44 44 | +45 45 | ## No errors +46 46 | int(round(0, 3.14)) +47 |-int(round(0, non_literal)) + 47 |+round(0, non_literal) +48 48 | int(round(0, 0), base) +49 49 | int(round(0, 0, extra=keyword)) +50 50 | int(round(0.1, 0)) + +RUF046.py:49:1: RUF046 [*] Value being casted is already an integer + | +47 | int(round(0, non_literal)) +48 | int(round(0, 0), base) +49 | int(round(0, 0, extra=keyword)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF046 +50 | int(round(0.1, 0)) + | + = help: Remove unnecessary conversion to `int` + +ℹ Unsafe fix +46 46 | int(round(0, 3.14)) +47 47 | int(round(0, non_literal)) +48 48 | int(round(0, 0), base) +49 |-int(round(0, 0, extra=keyword)) + 49 |+round(0, 0, extra=keyword) +50 50 | int(round(0.1, 0)) + +RUF046.py:50:1: RUF046 [*] Value being casted is already an integer + | +48 | int(round(0, 0), base) +49 | int(round(0, 0, extra=keyword)) +50 | int(round(0.1, 0)) + | ^^^^^^^^^^^^^^^^^^ RUF046 + | + = help: Remove unnecessary conversion to `int` + +ℹ Unsafe fix +47 47 | int(round(0, non_literal)) +48 48 | int(round(0, 0), base) +49 49 | int(round(0, 0, extra=keyword)) +50 |-int(round(0.1, 0)) + 50 |+round(0.1, 0)