diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB164.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB164.py index 9a03919ca9..81422d2cf8 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB164.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB164.py @@ -64,3 +64,8 @@ _ = Decimal.from_float(True) _ = Decimal.from_float(float("-nan")) _ = Decimal.from_float(float("\x2dnan")) _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan")) + +# See: https://github.com/astral-sh/ruff/issues/21257 +# fixes must be safe +_ = Fraction.from_float(f=4.2) +_ = Fraction.from_decimal(dec=4) \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/refurb/rules/unnecessary_from_float.rs b/crates/ruff_linter/src/rules/refurb/rules/unnecessary_from_float.rs index e34357bd55..38184c8fd3 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/unnecessary_from_float.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/unnecessary_from_float.rs @@ -149,10 +149,9 @@ pub(crate) fn unnecessary_from_float(checker: &Checker, call: &ExprCall) { // Check if we should suppress the fix due to type validation concerns let is_type_safe = is_valid_argument_type(arg_value, method_name, constructor, checker); - let has_keywords = !call.arguments.keywords.is_empty(); // Determine fix safety - let applicability = if is_type_safe && !has_keywords { + let applicability = if is_type_safe { Applicability::Safe } else { Applicability::Unsafe @@ -210,21 +209,27 @@ fn is_valid_argument_type( _ => false, }, // Fraction.from_decimal accepts int, bool, Decimal - (MethodName::FromDecimal, Constructor::Fraction) => match resolved_type { - ResolvedPythonType::Atom(PythonType::Number( - NumberLike::Integer | NumberLike::Bool, - )) => true, - ResolvedPythonType::Unknown => is_int, - _ => { - // Check if it's a Decimal instance - arg_expr - .as_call_expr() - .and_then(|call| semantic.resolve_qualified_name(&call.func)) - .is_some_and(|qualified_name| { - matches!(qualified_name.segments(), ["decimal", "Decimal"]) - }) + (MethodName::FromDecimal, Constructor::Fraction) => { + // First check if it's a Decimal constructor call + let is_decimal_call = arg_expr + .as_call_expr() + .and_then(|call| semantic.resolve_qualified_name(&call.func)) + .is_some_and(|qualified_name| { + matches!(qualified_name.segments(), ["decimal", "Decimal"]) + }); + + if is_decimal_call { + return true; } - }, + + match resolved_type { + ResolvedPythonType::Atom(PythonType::Number( + NumberLike::Integer | NumberLike::Bool, + )) => true, + ResolvedPythonType::Unknown => is_int, + _ => false, + } + } _ => false, } } @@ -274,7 +279,7 @@ fn handle_non_finite_float_special_case( return None; } - let Expr::Call(ast::ExprCall { + let Expr::Call(ExprCall { func, arguments, .. }) = arg_value else { diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB164_FURB164.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB164_FURB164.py.snap index e917928a64..7bd2ce8225 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB164_FURB164.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB164_FURB164.py.snap @@ -99,7 +99,6 @@ help: Replace with `Fraction` constructor 12 | _ = Fraction.from_decimal(Decimal("-4.2")) 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2)) 14 | _ = Decimal.from_float(0.1) -note: This is an unsafe fix and may change runtime behavior FURB164 [*] Verbose method `from_decimal` in `Fraction` construction --> FURB164.py:12:5 @@ -120,7 +119,6 @@ help: Replace with `Fraction` constructor 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2)) 14 | _ = Decimal.from_float(0.1) 15 | _ = Decimal.from_float(-0.5) -note: This is an unsafe fix and may change runtime behavior FURB164 [*] Verbose method `from_decimal` in `Fraction` construction --> FURB164.py:13:5 @@ -484,7 +482,6 @@ help: Replace with `Fraction` constructor 32 | _ = Decimal.from_float(f=4.2) 33 | 34 | # Cases with invalid argument counts - should not get fixes -note: This is an unsafe fix and may change runtime behavior FURB164 Verbose method `from_float` in `Decimal` construction --> FURB164.py:32:5 @@ -658,6 +655,7 @@ help: Replace with `Decimal` constructor 64 + _ = Decimal("nan") 65 | _ = Decimal.from_float(float("\x2dnan")) 66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan")) +67 | FURB164 [*] Verbose method `from_float` in `Decimal` construction --> FURB164.py:65:5 @@ -675,6 +673,8 @@ help: Replace with `Decimal` constructor - _ = Decimal.from_float(float("\x2dnan")) 65 + _ = Decimal("nan") 66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan")) +67 | +68 | # See: https://github.com/astral-sh/ruff/issues/21257 FURB164 [*] Verbose method `from_float` in `Decimal` construction --> FURB164.py:66:5 @@ -683,6 +683,8 @@ FURB164 [*] Verbose method `from_float` in `Decimal` construction 65 | _ = Decimal.from_float(float("\x2dnan")) 66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan")) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +67 | +68 | # See: https://github.com/astral-sh/ruff/issues/21257 | help: Replace with `Decimal` constructor 63 | # Cases with non-finite floats - should produce safe fixes @@ -690,3 +692,38 @@ help: Replace with `Decimal` constructor 65 | _ = Decimal.from_float(float("\x2dnan")) - _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan")) 66 + _ = Decimal("nan") +67 | +68 | # See: https://github.com/astral-sh/ruff/issues/21257 +69 | # fixes must be safe + +FURB164 [*] Verbose method `from_float` in `Fraction` construction + --> FURB164.py:70:5 + | +68 | # See: https://github.com/astral-sh/ruff/issues/21257 +69 | # fixes must be safe +70 | _ = Fraction.from_float(f=4.2) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +71 | _ = Fraction.from_decimal(dec=4) + | +help: Replace with `Fraction` constructor +67 | +68 | # See: https://github.com/astral-sh/ruff/issues/21257 +69 | # fixes must be safe + - _ = Fraction.from_float(f=4.2) +70 + _ = Fraction(4.2) +71 | _ = Fraction.from_decimal(dec=4) + +FURB164 [*] Verbose method `from_decimal` in `Fraction` construction + --> FURB164.py:71:5 + | +69 | # fixes must be safe +70 | _ = Fraction.from_float(f=4.2) +71 | _ = Fraction.from_decimal(dec=4) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: Replace with `Fraction` constructor +68 | # See: https://github.com/astral-sh/ruff/issues/21257 +69 | # fixes must be safe +70 | _ = Fraction.from_float(f=4.2) + - _ = Fraction.from_decimal(dec=4) +71 + _ = Fraction(4)