diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF046.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF046.py index e558cf05148c3..932782e0d3194 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF046.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF046.py @@ -1,9 +1,12 @@ import math +inferred_int = 1 +inferred_float = 1. + + ### Safely fixable -# Arguments are not checked int(id()) int(len([])) int(ord(foo)) @@ -17,6 +20,15 @@ int(math.isqrt()) int(math.perm()) +int(round(1, 0)) +int(round(1, 10)) + +int(round(1)) +int(round(1, None)) + +int(round(1.)) +int(round(1., None)) + ### Unsafe @@ -24,27 +36,35 @@ int(math.floor()) int(math.trunc()) +int(round(inferred_int, 0)) +int(round(inferred_int, 10)) -### `round()` +int(round(inferred_int)) +int(round(inferred_int, None)) -## Errors -int(round(0)) -int(round(0, 0)) -int(round(0, None)) +int(round(inferred_float)) +int(round(inferred_float, None)) -int(round(0.1)) -int(round(0.1, None)) +int(round(unknown)) +int(round(unknown, None)) -# Argument type is not checked -foo = type("Foo", (), {"__round__": lambda self: 4.2})() -int(round(foo)) -int(round(foo, 0)) -int(round(foo, None)) +### No errors + +int(round(1, unknown)) +int(round(1., unknown)) + +int(round(1., 0)) +int(round(inferred_float, 0)) + +int(round(inferred_int, unknown)) +int(round(inferred_float, unknown)) + +int(round(unknown, 0)) +int(round(unknown, unknown)) -## No errors int(round(0, 3.14)) -int(round(0, non_literal)) +int(round(inferred_int, 3.14)) + int(round(0, 0), base) int(round(0, 0, extra=keyword)) -int(round(0.1, 0)) 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..b533c7ea45dcd 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,7 +1,7 @@ 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, ExprName, ExprNumberLiteral, Number}; +use ruff_python_ast::{Arguments, Expr, ExprCall, ExprNumberLiteral, Number}; use ruff_python_semantic::analyze::typing; use ruff_python_semantic::SemanticModel; use ruff_text_size::TextRange; @@ -74,7 +74,7 @@ pub(crate) fn unnecessary_cast_to_int(checker: &mut Checker, call: &ExprCall) { // Depends on `ndigits` and `number.__round__` ["" | "builtins", "round"] => { - if let Some(fix) = replace_with_shortened_round_call(checker, outer_range, arguments) { + if let Some(fix) = replace_with_round(checker, outer_range, inner_range, arguments) { fix } else { return; @@ -89,9 +89,9 @@ pub(crate) fn unnecessary_cast_to_int(checker: &mut Checker, call: &ExprCall) { _ => 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,12 +117,29 @@ 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( +/// 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, +} + +fn replace_with_round( checker: &Checker, outer_range: TextRange, + inner_range: TextRange, arguments: &Arguments, ) -> Option { if arguments.len() > 2 { @@ -132,48 +149,56 @@ fn replace_with_shortened_round_call( 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, - }; + let number_kind = match number { + Expr::Name(name) => { + let semantic = checker.semantic(); - match ndigits { - Some(Expr::NumberLiteral(ExprNumberLiteral { value, .. })) - if is_literal_zero(value) && number_is_int => {} - Some(Expr::NoneLiteral(_)) | None => {} - _ => return None, - }; + 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, + } + } - let number_expr = checker.locator().slice(number); - let new_content = format!("round({number_expr})"); + Expr::NumberLiteral(ExprNumberLiteral { value, .. }) => match value { + Number::Int(..) => Rounded::LiteralInt, + Number::Float(..) => Rounded::LiteralFloat, + Number::Complex { .. } => Rounded::Other, + }, - let applicability = if number_is_int { - Applicability::Safe - } else { - Applicability::Unsafe + _ => Rounded::Other, }; - Some(Fix::applicable_edit( - Edit::range_replacement(new_content, outer_range), - applicability, - )) -} + let ndigits_kind = match ndigits { + None => Ndigits::NotGiven, + Some(Expr::NoneLiteral(_)) => Ndigits::LiteralNone, -fn is_int(semantic: &SemanticModel, name: &ExprName) -> bool { - let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else { - return false; + Some(Expr::NumberLiteral(ExprNumberLiteral { + value: Number::Int(..), + .. + })) => Ndigits::LiteralInt, + + _ => Ndigits::Other, }; - typing::is_int(binding, semantic) -} + let applicability = match (number_kind, ndigits_kind) { + (Rounded::LiteralInt, Ndigits::LiteralInt) + | (Rounded::LiteralInt | Rounded::LiteralFloat, Ndigits::NotGiven | Ndigits::LiteralNone) => { + Applicability::Safe + } + + (Rounded::InferredInt, Ndigits::LiteralInt) + | ( + Rounded::InferredInt | Rounded::InferredFloat | Rounded::Other, + Ndigits::NotGiven | Ndigits::LiteralNone, + ) => Applicability::Unsafe, -fn is_literal_zero(value: &Number) -> bool { - let Number::Int(int) = value else { - return false; + _ => return None, }; - matches!(int.as_u8(), Some(0)) + let edit = replace_with_inner(checker, outer_range, inner_range); + + Some(Fix::applicable_edit(edit, applicability)) } fn replace_with_inner(checker: &Checker, outer_range: TextRange, inner_range: TextRange) -> Edit { 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..867ba99180dc2 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 @@ -2,429 +2,566 @@ source: crates/ruff_linter/src/rules/ruff/mod.rs snapshot_kind: text --- -RUF046.py:7:1: RUF046 [*] Value being casted is already an integer - | -6 | # Arguments are not checked -7 | int(id()) - | ^^^^^^^^^ RUF046 -8 | int(len([])) -9 | int(ord(foo)) - | - = help: Remove unnecessary conversion to `int` +RUF046.py:10:1: RUF046 [*] Value being casted is already an integer + | + 8 | ### Safely fixable + 9 | +10 | int(id()) + | ^^^^^^^^^ RUF046 +11 | int(len([])) +12 | int(ord(foo)) + | + = help: Remove unnecessary conversion to `int` ℹ Safe fix -4 4 | ### Safely fixable -5 5 | -6 6 | # Arguments are not checked -7 |-int(id()) - 7 |+id() -8 8 | int(len([])) -9 9 | int(ord(foo)) -10 10 | int(hash(foo, bar)) - -RUF046.py:8:1: RUF046 [*] Value being casted is already an integer - | - 6 | # Arguments are not checked - 7 | int(id()) - 8 | int(len([])) +7 7 | +8 8 | ### Safely fixable +9 9 | +10 |-int(id()) + 10 |+id() +11 11 | int(len([])) +12 12 | int(ord(foo)) +13 13 | int(hash(foo, bar)) + +RUF046.py:11:1: RUF046 [*] Value being casted is already an integer + | +10 | int(id()) +11 | int(len([])) | ^^^^^^^^^^^^ RUF046 - 9 | int(ord(foo)) -10 | int(hash(foo, bar)) +12 | int(ord(foo)) +13 | int(hash(foo, bar)) | = help: Remove unnecessary conversion to `int` ℹ Safe fix -5 5 | -6 6 | # Arguments are not checked -7 7 | int(id()) -8 |-int(len([])) - 8 |+len([]) -9 9 | int(ord(foo)) -10 10 | int(hash(foo, bar)) -11 11 | int(int('')) - -RUF046.py:9:1: RUF046 [*] Value being casted is already an integer - | - 7 | int(id()) - 8 | int(len([])) - 9 | int(ord(foo)) +8 8 | ### Safely fixable +9 9 | +10 10 | int(id()) +11 |-int(len([])) + 11 |+len([]) +12 12 | int(ord(foo)) +13 13 | int(hash(foo, bar)) +14 14 | int(int('')) + +RUF046.py:12:1: RUF046 [*] Value being casted is already an integer + | +10 | int(id()) +11 | int(len([])) +12 | int(ord(foo)) | ^^^^^^^^^^^^^ RUF046 -10 | int(hash(foo, bar)) -11 | int(int('')) +13 | int(hash(foo, bar)) +14 | int(int('')) | = help: Remove unnecessary conversion to `int` ℹ Safe fix -6 6 | # Arguments are not checked -7 7 | int(id()) -8 8 | int(len([])) -9 |-int(ord(foo)) - 9 |+ord(foo) -10 10 | int(hash(foo, bar)) -11 11 | int(int('')) -12 12 | +9 9 | +10 10 | int(id()) +11 11 | int(len([])) +12 |-int(ord(foo)) + 12 |+ord(foo) +13 13 | int(hash(foo, bar)) +14 14 | int(int('')) +15 15 | -RUF046.py:10:1: RUF046 [*] Value being casted is already an integer +RUF046.py:13:1: RUF046 [*] Value being casted is already an integer | - 8 | int(len([])) - 9 | int(ord(foo)) -10 | int(hash(foo, bar)) +11 | int(len([])) +12 | int(ord(foo)) +13 | int(hash(foo, bar)) | ^^^^^^^^^^^^^^^^^^^ RUF046 -11 | int(int('')) +14 | int(int('')) | = help: Remove unnecessary conversion to `int` ℹ Safe fix -7 7 | int(id()) -8 8 | int(len([])) -9 9 | int(ord(foo)) -10 |-int(hash(foo, bar)) - 10 |+hash(foo, bar) -11 11 | int(int('')) -12 12 | -13 13 | int(math.comb()) +10 10 | int(id()) +11 11 | int(len([])) +12 12 | int(ord(foo)) +13 |-int(hash(foo, bar)) + 13 |+hash(foo, bar) +14 14 | int(int('')) +15 15 | +16 16 | int(math.comb()) -RUF046.py:11:1: RUF046 [*] Value being casted is already an integer +RUF046.py:14:1: RUF046 [*] Value being casted is already an integer | - 9 | int(ord(foo)) -10 | int(hash(foo, bar)) -11 | int(int('')) +12 | int(ord(foo)) +13 | int(hash(foo, bar)) +14 | int(int('')) | ^^^^^^^^^^^^ RUF046 -12 | -13 | int(math.comb()) +15 | +16 | int(math.comb()) | = help: Remove unnecessary conversion to `int` ℹ Safe fix -8 8 | int(len([])) -9 9 | int(ord(foo)) -10 10 | int(hash(foo, bar)) -11 |-int(int('')) - 11 |+int('') -12 12 | -13 13 | int(math.comb()) -14 14 | int(math.factorial()) +11 11 | int(len([])) +12 12 | int(ord(foo)) +13 13 | int(hash(foo, bar)) +14 |-int(int('')) + 14 |+int('') +15 15 | +16 16 | int(math.comb()) +17 17 | int(math.factorial()) -RUF046.py:13:1: RUF046 [*] Value being casted is already an integer +RUF046.py:16:1: RUF046 [*] Value being casted is already an integer | -11 | int(int('')) -12 | -13 | int(math.comb()) +14 | int(int('')) +15 | +16 | int(math.comb()) | ^^^^^^^^^^^^^^^^ RUF046 -14 | int(math.factorial()) -15 | int(math.gcd()) +17 | int(math.factorial()) +18 | int(math.gcd()) | = help: Remove unnecessary conversion to `int` ℹ Safe fix -10 10 | int(hash(foo, bar)) -11 11 | int(int('')) -12 12 | -13 |-int(math.comb()) - 13 |+math.comb() -14 14 | int(math.factorial()) -15 15 | int(math.gcd()) -16 16 | int(math.lcm()) +13 13 | int(hash(foo, bar)) +14 14 | int(int('')) +15 15 | +16 |-int(math.comb()) + 16 |+math.comb() +17 17 | int(math.factorial()) +18 18 | int(math.gcd()) +19 19 | int(math.lcm()) -RUF046.py:14:1: RUF046 [*] Value being casted is already an integer +RUF046.py:17:1: RUF046 [*] Value being casted is already an integer | -13 | int(math.comb()) -14 | int(math.factorial()) +16 | int(math.comb()) +17 | int(math.factorial()) | ^^^^^^^^^^^^^^^^^^^^^ RUF046 -15 | int(math.gcd()) -16 | int(math.lcm()) +18 | int(math.gcd()) +19 | int(math.lcm()) | = help: Remove unnecessary conversion to `int` ℹ Safe fix -11 11 | int(int('')) -12 12 | -13 13 | int(math.comb()) -14 |-int(math.factorial()) - 14 |+math.factorial() -15 15 | int(math.gcd()) -16 16 | int(math.lcm()) -17 17 | int(math.isqrt()) - -RUF046.py:15:1: RUF046 [*] Value being casted is already an integer - | -13 | int(math.comb()) -14 | int(math.factorial()) -15 | int(math.gcd()) +14 14 | int(int('')) +15 15 | +16 16 | int(math.comb()) +17 |-int(math.factorial()) + 17 |+math.factorial() +18 18 | int(math.gcd()) +19 19 | int(math.lcm()) +20 20 | int(math.isqrt()) + +RUF046.py:18:1: RUF046 [*] Value being casted is already an integer + | +16 | int(math.comb()) +17 | int(math.factorial()) +18 | int(math.gcd()) | ^^^^^^^^^^^^^^^ RUF046 -16 | int(math.lcm()) -17 | int(math.isqrt()) +19 | int(math.lcm()) +20 | int(math.isqrt()) | = help: Remove unnecessary conversion to `int` ℹ Safe fix -12 12 | -13 13 | int(math.comb()) -14 14 | int(math.factorial()) -15 |-int(math.gcd()) - 15 |+math.gcd() -16 16 | int(math.lcm()) -17 17 | int(math.isqrt()) -18 18 | int(math.perm()) - -RUF046.py:16:1: RUF046 [*] Value being casted is already an integer - | -14 | int(math.factorial()) -15 | int(math.gcd()) -16 | int(math.lcm()) +15 15 | +16 16 | int(math.comb()) +17 17 | int(math.factorial()) +18 |-int(math.gcd()) + 18 |+math.gcd() +19 19 | int(math.lcm()) +20 20 | int(math.isqrt()) +21 21 | int(math.perm()) + +RUF046.py:19:1: RUF046 [*] Value being casted is already an integer + | +17 | int(math.factorial()) +18 | int(math.gcd()) +19 | int(math.lcm()) | ^^^^^^^^^^^^^^^ RUF046 -17 | int(math.isqrt()) -18 | int(math.perm()) +20 | int(math.isqrt()) +21 | int(math.perm()) | = help: Remove unnecessary conversion to `int` ℹ Safe fix -13 13 | int(math.comb()) -14 14 | int(math.factorial()) -15 15 | int(math.gcd()) -16 |-int(math.lcm()) - 16 |+math.lcm() -17 17 | int(math.isqrt()) -18 18 | int(math.perm()) -19 19 | +16 16 | int(math.comb()) +17 17 | int(math.factorial()) +18 18 | int(math.gcd()) +19 |-int(math.lcm()) + 19 |+math.lcm() +20 20 | int(math.isqrt()) +21 21 | int(math.perm()) +22 22 | -RUF046.py:17:1: RUF046 [*] Value being casted is already an integer +RUF046.py:20:1: RUF046 [*] Value being casted is already an integer | -15 | int(math.gcd()) -16 | int(math.lcm()) -17 | int(math.isqrt()) +18 | int(math.gcd()) +19 | int(math.lcm()) +20 | int(math.isqrt()) | ^^^^^^^^^^^^^^^^^ RUF046 -18 | int(math.perm()) +21 | int(math.perm()) | = help: Remove unnecessary conversion to `int` ℹ Safe fix -14 14 | int(math.factorial()) -15 15 | int(math.gcd()) -16 16 | int(math.lcm()) -17 |-int(math.isqrt()) - 17 |+math.isqrt() -18 18 | int(math.perm()) -19 19 | -20 20 | +17 17 | int(math.factorial()) +18 18 | int(math.gcd()) +19 19 | int(math.lcm()) +20 |-int(math.isqrt()) + 20 |+math.isqrt() +21 21 | int(math.perm()) +22 22 | +23 23 | int(round(1, 0)) -RUF046.py:18:1: RUF046 [*] Value being casted is already an integer +RUF046.py:21:1: RUF046 [*] Value being casted is already an integer | -16 | int(math.lcm()) -17 | int(math.isqrt()) -18 | int(math.perm()) +19 | int(math.lcm()) +20 | int(math.isqrt()) +21 | int(math.perm()) | ^^^^^^^^^^^^^^^^ RUF046 +22 | +23 | int(round(1, 0)) | = help: Remove unnecessary conversion to `int` ℹ Safe fix -15 15 | int(math.gcd()) -16 16 | int(math.lcm()) -17 17 | int(math.isqrt()) -18 |-int(math.perm()) - 18 |+math.perm() -19 19 | -20 20 | -21 21 | ### Unsafe +18 18 | int(math.gcd()) +19 19 | int(math.lcm()) +20 20 | int(math.isqrt()) +21 |-int(math.perm()) + 21 |+math.perm() +22 22 | +23 23 | int(round(1, 0)) +24 24 | int(round(1, 10)) RUF046.py:23:1: RUF046 [*] Value being casted is already an integer | -21 | ### Unsafe +21 | int(math.perm()) 22 | -23 | int(math.ceil()) +23 | int(round(1, 0)) | ^^^^^^^^^^^^^^^^ RUF046 -24 | int(math.floor()) -25 | int(math.trunc()) +24 | int(round(1, 10)) | = help: Remove unnecessary conversion to `int` -ℹ Unsafe fix -20 20 | -21 21 | ### Unsafe +ℹ Safe fix +20 20 | int(math.isqrt()) +21 21 | int(math.perm()) 22 22 | -23 |-int(math.ceil()) - 23 |+math.ceil() -24 24 | int(math.floor()) -25 25 | int(math.trunc()) -26 26 | +23 |-int(round(1, 0)) + 23 |+round(1, 0) +24 24 | int(round(1, 10)) +25 25 | +26 26 | int(round(1)) RUF046.py:24:1: RUF046 [*] Value being casted is already an integer | -23 | int(math.ceil()) -24 | int(math.floor()) +23 | int(round(1, 0)) +24 | int(round(1, 10)) | ^^^^^^^^^^^^^^^^^ RUF046 -25 | int(math.trunc()) +25 | +26 | int(round(1)) | = help: Remove unnecessary conversion to `int` -ℹ Unsafe fix -21 21 | ### Unsafe +ℹ Safe fix +21 21 | int(math.perm()) 22 22 | -23 23 | int(math.ceil()) -24 |-int(math.floor()) - 24 |+math.floor() -25 25 | int(math.trunc()) -26 26 | -27 27 | - -RUF046.py:25:1: RUF046 [*] Value being casted is already an integer - | -23 | int(math.ceil()) -24 | int(math.floor()) -25 | int(math.trunc()) - | ^^^^^^^^^^^^^^^^^ RUF046 +23 23 | int(round(1, 0)) +24 |-int(round(1, 10)) + 24 |+round(1, 10) +25 25 | +26 26 | int(round(1)) +27 27 | int(round(1, None)) + +RUF046.py:26:1: RUF046 [*] Value being casted is already an integer + | +24 | int(round(1, 10)) +25 | +26 | int(round(1)) + | ^^^^^^^^^^^^^ RUF046 +27 | int(round(1, None)) | = help: Remove unnecessary conversion to `int` -ℹ Unsafe fix -22 22 | -23 23 | int(math.ceil()) -24 24 | int(math.floor()) -25 |-int(math.trunc()) - 25 |+math.trunc() -26 26 | -27 27 | -28 28 | ### `round()` - -RUF046.py:31:1: RUF046 [*] Value being casted is already an integer - | -30 | ## Errors -31 | int(round(0)) - | ^^^^^^^^^^^^^ RUF046 -32 | int(round(0, 0)) -33 | int(round(0, None)) +ℹ Safe fix +23 23 | int(round(1, 0)) +24 24 | int(round(1, 10)) +25 25 | +26 |-int(round(1)) + 26 |+round(1) +27 27 | int(round(1, None)) +28 28 | +29 29 | int(round(1.)) + +RUF046.py:27:1: RUF046 [*] Value being casted is already an integer + | +26 | int(round(1)) +27 | int(round(1, None)) + | ^^^^^^^^^^^^^^^^^^^ RUF046 +28 | +29 | int(round(1.)) | = help: Remove unnecessary conversion to `int` ℹ Safe fix -28 28 | ### `round()` -29 29 | -30 30 | ## Errors -31 |-int(round(0)) - 31 |+round(0) -32 32 | int(round(0, 0)) -33 33 | int(round(0, None)) -34 34 | - -RUF046.py:32:1: RUF046 [*] Value being casted is already an integer - | -30 | ## Errors -31 | int(round(0)) -32 | int(round(0, 0)) - | ^^^^^^^^^^^^^^^^ RUF046 -33 | int(round(0, None)) +24 24 | int(round(1, 10)) +25 25 | +26 26 | int(round(1)) +27 |-int(round(1, None)) + 27 |+round(1, None) +28 28 | +29 29 | int(round(1.)) +30 30 | int(round(1., None)) + +RUF046.py:29:1: RUF046 [*] Value being casted is already an integer + | +27 | int(round(1, None)) +28 | +29 | int(round(1.)) + | ^^^^^^^^^^^^^^ RUF046 +30 | int(round(1., None)) | = help: Remove unnecessary conversion to `int` ℹ Safe fix -29 29 | -30 30 | ## Errors -31 31 | int(round(0)) -32 |-int(round(0, 0)) - 32 |+round(0) -33 33 | int(round(0, None)) -34 34 | -35 35 | int(round(0.1)) +26 26 | int(round(1)) +27 27 | int(round(1, None)) +28 28 | +29 |-int(round(1.)) + 29 |+round(1.) +30 30 | int(round(1., None)) +31 31 | +32 32 | -RUF046.py:33:1: RUF046 [*] Value being casted is already an integer +RUF046.py:30:1: RUF046 [*] Value being casted is already an integer | -31 | int(round(0)) -32 | int(round(0, 0)) -33 | int(round(0, None)) - | ^^^^^^^^^^^^^^^^^^^ RUF046 -34 | -35 | int(round(0.1)) +29 | int(round(1.)) +30 | int(round(1., None)) + | ^^^^^^^^^^^^^^^^^^^^ RUF046 | = help: Remove unnecessary conversion to `int` ℹ Safe fix -30 30 | ## Errors -31 31 | int(round(0)) -32 32 | int(round(0, 0)) -33 |-int(round(0, None)) - 33 |+round(0) -34 34 | -35 35 | int(round(0.1)) -36 36 | int(round(0.1, None)) +27 27 | int(round(1, None)) +28 28 | +29 29 | int(round(1.)) +30 |-int(round(1., None)) + 30 |+round(1., None) +31 31 | +32 32 | +33 33 | ### Unsafe RUF046.py:35:1: RUF046 [*] Value being casted is already an integer | -33 | int(round(0, None)) +33 | ### Unsafe 34 | -35 | int(round(0.1)) - | ^^^^^^^^^^^^^^^ RUF046 -36 | int(round(0.1, None)) +35 | int(math.ceil()) + | ^^^^^^^^^^^^^^^^ RUF046 +36 | int(math.floor()) +37 | int(math.trunc()) | = help: Remove unnecessary conversion to `int` ℹ Unsafe fix -32 32 | int(round(0, 0)) -33 33 | int(round(0, None)) +32 32 | +33 33 | ### Unsafe 34 34 | -35 |-int(round(0.1)) - 35 |+round(0.1) -36 36 | int(round(0.1, None)) -37 37 | -38 38 | # Argument type is not checked +35 |-int(math.ceil()) + 35 |+math.ceil() +36 36 | int(math.floor()) +37 37 | int(math.trunc()) +38 38 | RUF046.py:36:1: RUF046 [*] Value being casted is already an integer | -35 | int(round(0.1)) -36 | int(round(0.1, None)) - | ^^^^^^^^^^^^^^^^^^^^^ RUF046 -37 | -38 | # Argument type is not checked +35 | int(math.ceil()) +36 | int(math.floor()) + | ^^^^^^^^^^^^^^^^^ RUF046 +37 | int(math.trunc()) | = help: Remove unnecessary conversion to `int` ℹ Unsafe fix -33 33 | int(round(0, None)) +33 33 | ### Unsafe 34 34 | -35 35 | int(round(0.1)) -36 |-int(round(0.1, None)) - 36 |+round(0.1) -37 37 | -38 38 | # Argument type is not checked -39 39 | foo = type("Foo", (), {"__round__": lambda self: 4.2})() - -RUF046.py:41:1: RUF046 [*] Value being casted is already an integer - | -39 | foo = type("Foo", (), {"__round__": lambda self: 4.2})() -40 | -41 | int(round(foo)) - | ^^^^^^^^^^^^^^^ RUF046 -42 | int(round(foo, 0)) -43 | int(round(foo, None)) +35 35 | int(math.ceil()) +36 |-int(math.floor()) + 36 |+math.floor() +37 37 | int(math.trunc()) +38 38 | +39 39 | int(round(inferred_int, 0)) + +RUF046.py:37:1: RUF046 [*] Value being casted is already an integer + | +35 | int(math.ceil()) +36 | int(math.floor()) +37 | int(math.trunc()) + | ^^^^^^^^^^^^^^^^^ RUF046 +38 | +39 | int(round(inferred_int, 0)) | = help: Remove unnecessary conversion to `int` ℹ Unsafe fix -38 38 | # Argument type is not checked -39 39 | foo = type("Foo", (), {"__round__": lambda self: 4.2})() -40 40 | -41 |-int(round(foo)) - 41 |+round(foo) -42 42 | int(round(foo, 0)) -43 43 | int(round(foo, None)) +34 34 | +35 35 | int(math.ceil()) +36 36 | int(math.floor()) +37 |-int(math.trunc()) + 37 |+math.trunc() +38 38 | +39 39 | int(round(inferred_int, 0)) +40 40 | int(round(inferred_int, 10)) + +RUF046.py:39:1: RUF046 [*] Value being casted is already an integer + | +37 | int(math.trunc()) +38 | +39 | int(round(inferred_int, 0)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF046 +40 | int(round(inferred_int, 10)) + | + = help: Remove unnecessary conversion to `int` + +ℹ Unsafe fix +36 36 | int(math.floor()) +37 37 | int(math.trunc()) +38 38 | +39 |-int(round(inferred_int, 0)) + 39 |+round(inferred_int, 0) +40 40 | int(round(inferred_int, 10)) +41 41 | +42 42 | int(round(inferred_int)) + +RUF046.py:40:1: RUF046 [*] Value being casted is already an integer + | +39 | int(round(inferred_int, 0)) +40 | int(round(inferred_int, 10)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF046 +41 | +42 | int(round(inferred_int)) + | + = help: Remove unnecessary conversion to `int` + +ℹ Unsafe fix +37 37 | int(math.trunc()) +38 38 | +39 39 | int(round(inferred_int, 0)) +40 |-int(round(inferred_int, 10)) + 40 |+round(inferred_int, 10) +41 41 | +42 42 | int(round(inferred_int)) +43 43 | int(round(inferred_int, None)) + +RUF046.py:42:1: RUF046 [*] Value being casted is already an integer + | +40 | int(round(inferred_int, 10)) +41 | +42 | int(round(inferred_int)) + | ^^^^^^^^^^^^^^^^^^^^^^^^ RUF046 +43 | int(round(inferred_int, None)) + | + = help: Remove unnecessary conversion to `int` + +ℹ Unsafe fix +39 39 | int(round(inferred_int, 0)) +40 40 | int(round(inferred_int, 10)) +41 41 | +42 |-int(round(inferred_int)) + 42 |+round(inferred_int) +43 43 | int(round(inferred_int, None)) 44 44 | +45 45 | int(round(inferred_float)) RUF046.py:43:1: RUF046 [*] Value being casted is already an integer | -41 | int(round(foo)) -42 | int(round(foo, 0)) -43 | int(round(foo, None)) - | ^^^^^^^^^^^^^^^^^^^^^ RUF046 +42 | int(round(inferred_int)) +43 | int(round(inferred_int, None)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF046 +44 | +45 | int(round(inferred_float)) + | + = help: Remove unnecessary conversion to `int` + +ℹ Unsafe fix +40 40 | int(round(inferred_int, 10)) +41 41 | +42 42 | int(round(inferred_int)) +43 |-int(round(inferred_int, None)) + 43 |+round(inferred_int, None) +44 44 | +45 45 | int(round(inferred_float)) +46 46 | int(round(inferred_float, None)) + +RUF046.py:45:1: RUF046 [*] Value being casted is already an integer + | +43 | int(round(inferred_int, None)) 44 | -45 | ## No errors +45 | int(round(inferred_float)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF046 +46 | int(round(inferred_float, None)) + | + = help: Remove unnecessary conversion to `int` + +ℹ Unsafe fix +42 42 | int(round(inferred_int)) +43 43 | int(round(inferred_int, None)) +44 44 | +45 |-int(round(inferred_float)) + 45 |+round(inferred_float) +46 46 | int(round(inferred_float, None)) +47 47 | +48 48 | int(round(unknown)) + +RUF046.py:46:1: RUF046 [*] Value being casted is already an integer + | +45 | int(round(inferred_float)) +46 | int(round(inferred_float, None)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF046 +47 | +48 | int(round(unknown)) | = help: Remove unnecessary conversion to `int` ℹ Unsafe fix -40 40 | -41 41 | int(round(foo)) -42 42 | int(round(foo, 0)) -43 |-int(round(foo, None)) - 43 |+round(foo) +43 43 | int(round(inferred_int, None)) 44 44 | -45 45 | ## No errors -46 46 | int(round(0, 3.14)) +45 45 | int(round(inferred_float)) +46 |-int(round(inferred_float, None)) + 46 |+round(inferred_float, None) +47 47 | +48 48 | int(round(unknown)) +49 49 | int(round(unknown, None)) + +RUF046.py:48:1: RUF046 [*] Value being casted is already an integer + | +46 | int(round(inferred_float, None)) +47 | +48 | int(round(unknown)) + | ^^^^^^^^^^^^^^^^^^^ RUF046 +49 | int(round(unknown, None)) + | + = help: Remove unnecessary conversion to `int` + +ℹ Unsafe fix +45 45 | int(round(inferred_float)) +46 46 | int(round(inferred_float, None)) +47 47 | +48 |-int(round(unknown)) + 48 |+round(unknown) +49 49 | int(round(unknown, None)) +50 50 | +51 51 | + +RUF046.py:49:1: RUF046 [*] Value being casted is already an integer + | +48 | int(round(unknown)) +49 | int(round(unknown, None)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF046 + | + = help: Remove unnecessary conversion to `int` + +ℹ Unsafe fix +46 46 | int(round(inferred_float, None)) +47 47 | +48 48 | int(round(unknown)) +49 |-int(round(unknown, None)) + 49 |+round(unknown, None) +50 50 | +51 51 | +52 52 | ### No errors diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index 1dc71ab700504..b346390a8908c 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -665,6 +665,14 @@ impl BuiltinTypeChecker for IntChecker { const EXPR_TYPE: PythonType = PythonType::Number(NumberLike::Integer); } +struct FloatChecker; + +impl BuiltinTypeChecker for FloatChecker { + const BUILTIN_TYPE_NAME: &'static str = "float"; + const TYPING_NAME: Option<&'static str> = None; + const EXPR_TYPE: PythonType = PythonType::Number(NumberLike::Float); +} + pub struct IoBaseChecker; impl TypeChecker for IoBaseChecker { @@ -850,6 +858,11 @@ pub fn is_int(binding: &Binding, semantic: &SemanticModel) -> bool { check_type::(binding, semantic) } +/// Test whether the given binding can be considered an instance of `float`. +pub fn is_float(binding: &Binding, semantic: &SemanticModel) -> bool { + check_type::(binding, semantic) +} + /// Test whether the given binding can be considered a set. /// /// For this, we check what value might be associated with it through it's initialization and