diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB164.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB164.py index 745c1bf904..9a03919ca9 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB164.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB164.py @@ -55,3 +55,12 @@ _ = Decimal(0.1) _ = Decimal(-0.5) _ = Decimal(5.0) _ = decimal.Decimal(4.2) + +# Cases with int and bool - should produce safe fixes +_ = Decimal.from_float(1) +_ = Decimal.from_float(True) + +# Cases with non-finite floats - should produce safe fixes +_ = Decimal.from_float(float("-nan")) +_ = Decimal.from_float(float("\x2dnan")) +_ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan")) 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 fc2e525a01..03986fe1f3 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 @@ -180,18 +180,18 @@ fn is_valid_argument_type( typing::is_float(binding, semantic), ) }) - .unwrap_or_default() + .unwrap_or((false, false)) } else { (false, false) }; match (method_name, constructor) { - // Decimal.from_float accepts int, bool, float + // Decimal.from_float: Only int or bool are safe (float is unsafe due to FloatOperation trap) (MethodName::FromFloat, Constructor::Decimal) => match resolved_type { ResolvedPythonType::Atom(PythonType::Number( - NumberLike::Integer | NumberLike::Bool | NumberLike::Float, + NumberLike::Integer | NumberLike::Bool, )) => true, - ResolvedPythonType::Unknown => is_int || is_float, + ResolvedPythonType::Unknown => is_int, _ => false, }, // Fraction.from_float accepts int, bool, float @@ -286,10 +286,8 @@ fn handle_non_finite_float_special_case( let [float_arg] = arguments.args.as_ref() else { return None; }; - as_non_finite_float_string_literal(float_arg)?; - - let replacement_arg = checker.locator().slice(float_arg).to_string(); - let replacement_text = format!("{constructor_name}({replacement_arg})"); + let normalized = as_non_finite_float_string_literal(float_arg)?; + let replacement_text = format!(r#"{constructor_name}("{normalized}")"#); Some(Edit::range_replacement(replacement_text, call.range())) } 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 13f94c45f9..c667687090 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 @@ -158,7 +158,7 @@ FURB164.py:13:27: FURB164 [*] Verbose method `from_float` in `Decimal` construct | = help: Replace with `Decimal` constructor -ℹ Safe fix +ℹ Unsafe fix 10 10 | _ = fractions.Fraction.from_float(4.2) 11 11 | _ = Fraction.from_decimal(Decimal("4.2")) 12 12 | _ = Fraction.from_decimal(Decimal("-4.2")) @@ -179,7 +179,7 @@ FURB164.py:14:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi | = help: Replace with `Decimal` constructor -ℹ Safe fix +ℹ Unsafe fix 11 11 | _ = Fraction.from_decimal(Decimal("4.2")) 12 12 | _ = Fraction.from_decimal(Decimal("-4.2")) 13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2)) @@ -200,7 +200,7 @@ FURB164.py:15:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi | = help: Replace with `Decimal` constructor -ℹ Safe fix +ℹ Unsafe fix 12 12 | _ = Fraction.from_decimal(Decimal("-4.2")) 13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2)) 14 14 | _ = Decimal.from_float(0.1) @@ -221,7 +221,7 @@ FURB164.py:16:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi | = help: Replace with `Decimal` constructor -ℹ Safe fix +ℹ Unsafe fix 13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2)) 14 14 | _ = Decimal.from_float(0.1) 15 15 | _ = Decimal.from_float(-0.5) @@ -242,7 +242,7 @@ FURB164.py:17:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi | = help: Replace with `Decimal` constructor -ℹ Safe fix +ℹ Unsafe fix 14 14 | _ = Decimal.from_float(0.1) 15 15 | _ = Decimal.from_float(-0.5) 16 16 | _ = Decimal.from_float(5.0) @@ -310,7 +310,7 @@ FURB164.py:20:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi 18 18 | _ = Decimal.from_float(float("inf")) 19 19 | _ = Decimal.from_float(float("-inf")) 20 |-_ = Decimal.from_float(float("Infinity")) - 20 |+_ = Decimal("Infinity") + 20 |+_ = Decimal("infinity") 21 21 | _ = Decimal.from_float(float("-Infinity")) 22 22 | _ = Decimal.from_float(float("nan")) 23 23 | _ = Decimal.from_float(float("-NaN ")) @@ -331,7 +331,7 @@ FURB164.py:21:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi 19 19 | _ = Decimal.from_float(float("-inf")) 20 20 | _ = Decimal.from_float(float("Infinity")) 21 |-_ = Decimal.from_float(float("-Infinity")) - 21 |+_ = Decimal("-Infinity") + 21 |+_ = Decimal("-infinity") 22 22 | _ = Decimal.from_float(float("nan")) 23 23 | _ = Decimal.from_float(float("-NaN ")) 24 24 | _ = Decimal.from_float(float(" \n+nan \t")) @@ -373,7 +373,7 @@ FURB164.py:23:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi 21 21 | _ = Decimal.from_float(float("-Infinity")) 22 22 | _ = Decimal.from_float(float("nan")) 23 |-_ = Decimal.from_float(float("-NaN ")) - 23 |+_ = Decimal("-NaN ") + 23 |+_ = Decimal("-nan") 24 24 | _ = Decimal.from_float(float(" \n+nan \t")) 25 25 | _ = Decimal.from_float(float(" iNf \n\t ")) 26 26 | _ = Decimal.from_float(float("  -inF\n \t")) @@ -394,7 +394,7 @@ FURB164.py:24:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi 22 22 | _ = Decimal.from_float(float("nan")) 23 23 | _ = Decimal.from_float(float("-NaN ")) 24 |-_ = Decimal.from_float(float(" \n+nan \t")) - 24 |+_ = Decimal(" \n+nan \t") + 24 |+_ = Decimal("+nan") 25 25 | _ = Decimal.from_float(float(" iNf \n\t ")) 26 26 | _ = Decimal.from_float(float("  -inF\n \t")) 27 27 | _ = Decimal.from_float(float(" InfinIty \n\t ")) @@ -415,7 +415,7 @@ FURB164.py:25:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi 23 23 | _ = Decimal.from_float(float("-NaN ")) 24 24 | _ = Decimal.from_float(float(" \n+nan \t")) 25 |-_ = Decimal.from_float(float(" iNf \n\t ")) - 25 |+_ = Decimal(" iNf \n\t ") + 25 |+_ = Decimal("inf") 26 26 | _ = Decimal.from_float(float("  -inF\n \t")) 27 27 | _ = Decimal.from_float(float(" InfinIty \n\t ")) 28 28 | _ = Decimal.from_float(float("  -InfinIty\n \t")) @@ -436,7 +436,7 @@ FURB164.py:26:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi 24 24 | _ = Decimal.from_float(float(" \n+nan \t")) 25 25 | _ = Decimal.from_float(float(" iNf \n\t ")) 26 |-_ = Decimal.from_float(float("  -inF\n \t")) - 26 |+_ = Decimal("  -inF\n \t") + 26 |+_ = Decimal("-inf") 27 27 | _ = Decimal.from_float(float(" InfinIty \n\t ")) 28 28 | _ = Decimal.from_float(float("  -InfinIty\n \t")) 29 29 | @@ -456,7 +456,7 @@ FURB164.py:27:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi 25 25 | _ = Decimal.from_float(float(" iNf \n\t ")) 26 26 | _ = Decimal.from_float(float("  -inF\n \t")) 27 |-_ = Decimal.from_float(float(" InfinIty \n\t ")) - 27 |+_ = Decimal(" InfinIty \n\t ") + 27 |+_ = Decimal("infinity") 28 28 | _ = Decimal.from_float(float("  -InfinIty\n \t")) 29 29 | 30 30 | # Cases with keyword arguments - should produce unsafe fixes @@ -477,7 +477,7 @@ FURB164.py:28:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi 26 26 | _ = Decimal.from_float(float("  -inF\n \t")) 27 27 | _ = Decimal.from_float(float(" InfinIty \n\t ")) 28 |-_ = Decimal.from_float(float("  -InfinIty\n \t")) - 28 |+_ = Decimal("  -InfinIty\n \t") + 28 |+_ = Decimal("-infinity") 29 29 | 30 30 | # Cases with keyword arguments - should produce unsafe fixes 31 31 | _ = Fraction.from_decimal(dec=Decimal("4.2")) @@ -612,3 +612,96 @@ FURB164.py:45:5: FURB164 [*] Verbose method `from_float` in `Fraction` construct 46 46 | 47 47 | # OK - should not trigger the rule 48 48 | _ = Fraction(0.1) + +FURB164.py:60:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction + | +59 | # Cases with int and bool - should produce safe fixes +60 | _ = Decimal.from_float(1) + | ^^^^^^^^^^^^^^^^^^^^^ FURB164 +61 | _ = Decimal.from_float(True) + | + = help: Replace with `Decimal` constructor + +ℹ Safe fix +57 57 | _ = decimal.Decimal(4.2) +58 58 | +59 59 | # Cases with int and bool - should produce safe fixes +60 |-_ = Decimal.from_float(1) + 60 |+_ = Decimal(1) +61 61 | _ = Decimal.from_float(True) +62 62 | +63 63 | # Cases with non-finite floats - should produce safe fixes + +FURB164.py:61:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction + | +59 | # Cases with int and bool - should produce safe fixes +60 | _ = Decimal.from_float(1) +61 | _ = Decimal.from_float(True) + | ^^^^^^^^^^^^^^^^^^^^^^^^ FURB164 +62 | +63 | # Cases with non-finite floats - should produce safe fixes + | + = help: Replace with `Decimal` constructor + +ℹ Safe fix +58 58 | +59 59 | # Cases with int and bool - should produce safe fixes +60 60 | _ = Decimal.from_float(1) +61 |-_ = Decimal.from_float(True) + 61 |+_ = Decimal(True) +62 62 | +63 63 | # Cases with non-finite floats - should produce safe fixes +64 64 | _ = Decimal.from_float(float("-nan")) + +FURB164.py:64:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction + | +63 | # Cases with non-finite floats - should produce safe fixes +64 | _ = Decimal.from_float(float("-nan")) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164 +65 | _ = Decimal.from_float(float("\x2dnan")) +66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan")) + | + = help: Replace with `Decimal` constructor + +ℹ Safe fix +61 61 | _ = Decimal.from_float(True) +62 62 | +63 63 | # Cases with non-finite floats - should produce safe fixes +64 |-_ = Decimal.from_float(float("-nan")) + 64 |+_ = Decimal("-nan") +65 65 | _ = Decimal.from_float(float("\x2dnan")) +66 66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan")) + +FURB164.py:65:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction + | +63 | # Cases with non-finite floats - should produce safe fixes +64 | _ = Decimal.from_float(float("-nan")) +65 | _ = Decimal.from_float(float("\x2dnan")) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164 +66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan")) + | + = help: Replace with `Decimal` constructor + +ℹ Safe fix +62 62 | +63 63 | # Cases with non-finite floats - should produce safe fixes +64 64 | _ = Decimal.from_float(float("-nan")) +65 |-_ = Decimal.from_float(float("\x2dnan")) + 65 |+_ = Decimal("-nan") +66 66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan")) + +FURB164.py:66:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction + | +64 | _ = Decimal.from_float(float("-nan")) +65 | _ = Decimal.from_float(float("\x2dnan")) +66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan")) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164 + | + = help: Replace with `Decimal` constructor + +ℹ Safe fix +63 63 | # Cases with non-finite floats - should produce safe fixes +64 64 | _ = Decimal.from_float(float("-nan")) +65 65 | _ = Decimal.from_float(float("\x2dnan")) +66 |-_ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan")) + 66 |+_ = Decimal("-nan")