Skip to content

[refurb] Mark fix unsafe and move to suspicious (FURB152) - #28405

Merged
ntBre merged 3 commits into
astral-sh:mainfrom
DeviousCardi:fix/28127-furb152-unsafe
Oct 6, 2026
Merged

ntBre merged 3 commits into
astral-sh:mainfrom
DeviousCardi:fix/28127-furb152-unsafe

Conversation

@DeviousCardi

@DeviousCardi DeviousCardi commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

matches_constant treats a literal as a match if it equals π, e, or τ rounded or truncated anywhere from 2 to 15 decimal places. Truncating π at two places gives 3.14, so 3.14 matches and gets rewritten to math.pi.

On its own that's just an aggressive heuristic. What made it bite is that the fix was always Fix::safe_edits, and ruff applies safe fixes under a plain --fix with nothing to review. The reporter had a crop-harvest constant of 3.14 that turned into math.pi (3.141592653589793), and a 2.718 that turned into math.e. Their numbers changed and nothing told them.

The fix here marks it unsafe, so every rewrite now needs --unsafe-fixes. I also moved the rule from correctness to suspicious, per @ntBre's comment.

References

Closes: #28127

Test Plan

cargo test -p ruff_linter refurb
    test result: ok. 45 passed; 0 failed

cargo clippy -p ruff_linter --all-targets -- -D warnings   # clean
cargo dev generate-all                                      # no changes
uv run --only-dev --locked prek run --files <changed files> # all hooks pass

Every fix in the FURB152 fixture is now unsafe.

…B152)

`math-constant` matches any float literal that rounds to `pi`, `e` or `tau`
at 2 to 15 decimal places, so `3.14` and `2.718` are reported alongside full
precision spellings. The fix was always marked safe, which meant `ruff check
--fix` silently rewrote those shorter literals to `math.pi` and `math.e` and
changed the values the program computes with. Measurements, thresholds and
test fixtures that happen to start with the same digits were altered without
review.

Mark the fix safe only when the literal already denotes the same float as the
constant, so substituting it cannot change a result, and unsafe otherwise.
Move the rule from the `correctness` category to `suspicious`, since matching
a constant's leading digits does not establish that the constant was meant.
@astral-sh-bot
astral-sh-bot Bot requested a review from ntBre September 8, 2026 02:33
@astral-sh-bot

astral-sh-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+125 -125 violations, +0 -0 fixes in 11 projects; 48 projects unchanged)

PlasmaPy/PlasmaPy (+2 -2 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ tests/formulary/test_lengths.py:104:15: math-constant Replace `3.14159` with `math.pi`
- tests/formulary/test_lengths.py:104:15: math-constant [*] Replace `3.14159` with `math.pi`
+ tests/particles/test_exceptions.py:304:13: math-constant Replace `3.14159` with `math.pi`
- tests/particles/test_exceptions.py:304:13: math-constant [*] Replace `3.14159` with `math.pi`

apache/airflow (+27 -27 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py:3189:38: math-constant Replace `3.14` with `math.pi`
- airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py:3189:38: math-constant [*] Replace `3.14` with `math.pi`
+ airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py:3193:28: math-constant Replace `3.14` with `math.pi`
- airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py:3193:28: math-constant [*] Replace `3.14` with `math.pi`
+ airflow-core/tests/unit/migrations/test_0055_remove_pickled_data_from_dagrun_table.py:104:17: math-constant Replace `3.14` with `math.pi`
- airflow-core/tests/unit/migrations/test_0055_remove_pickled_data_from_dagrun_table.py:104:17: math-constant [*] Replace `3.14` with `math.pi`
+ airflow-core/tests/unit/migrations/test_0055_remove_pickled_data_from_dagrun_table.py:96:22: math-constant Replace `3.14` with `math.pi`
- airflow-core/tests/unit/migrations/test_0055_remove_pickled_data_from_dagrun_table.py:96:22: math-constant [*] Replace `3.14` with `math.pi`
+ airflow-core/tests/unit/serialization/test_helpers.py:152:32: math-constant Replace `3.14` with `math.pi`
- airflow-core/tests/unit/serialization/test_helpers.py:152:32: math-constant [*] Replace `3.14` with `math.pi`
... 44 additional changes omitted for project

apache/superset (+15 -15 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ superset/mcp_service/chart/tool/get_chart_preview.py:941:44: math-constant Replace `6.28` with `math.tau`
- superset/mcp_service/chart/tool/get_chart_preview.py:941:44: math-constant [*] Replace `6.28` with `math.tau`
+ tests/integration_tests/result_set_tests.py:106:14: math-constant Replace `3.14` with `math.pi`
- tests/integration_tests/result_set_tests.py:106:14: math-constant [*] Replace `3.14` with `math.pi`
+ tests/unit_tests/db_engine_specs/test_base.py:1457:32: math-constant Replace `3.14` with `math.pi`
- tests/unit_tests/db_engine_specs/test_base.py:1457:32: math-constant [*] Replace `3.14` with `math.pi`
+ tests/unit_tests/jinja_context_test.py:2689:31: math-constant Replace `3.14` with `math.pi`
- tests/unit_tests/jinja_context_test.py:2689:31: math-constant [*] Replace `3.14` with `math.pi`
+ tests/unit_tests/jinja_context_test.py:2705:60: math-constant Replace `3.14` with `math.pi`
- tests/unit_tests/jinja_context_test.py:2705:60: math-constant [*] Replace `3.14` with `math.pi`
... 20 additional changes omitted for project

bokeh/bokeh (+2 -2 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ tests/unit/bokeh/core/property/test_color__property.py:126:34: math-constant Replace `3.14` with `math.pi`
- tests/unit/bokeh/core/property/test_color__property.py:126:34: math-constant [*] Replace `3.14` with `math.pi`
+ tests/unit/bokeh/core/test_properties.py:227:23: math-constant Replace `3.14` with `math.pi`
- tests/unit/bokeh/core/test_properties.py:227:23: math-constant [*] Replace `3.14` with `math.pi`

ibis-project/ibis (+36 -36 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ ibis/backends/clickhouse/tests/test_functions.py:120:50: math-constant Replace `3.14` with `math.pi`
- ibis/backends/clickhouse/tests/test_functions.py:120:50: math-constant [*] Replace `3.14` with `math.pi`
+ ibis/backends/clickhouse/tests/test_functions.py:120:57: math-constant Replace `3.14` with `math.pi`
- ibis/backends/clickhouse/tests/test_functions.py:120:57: math-constant [*] Replace `3.14` with `math.pi`
+ ibis/backends/impala/tests/test_udf.py:288:44: math-constant Replace `3.14` with `math.pi`
- ibis/backends/impala/tests/test_udf.py:288:44: math-constant [*] Replace `3.14` with `math.pi`
+ ibis/backends/impala/tests/test_udf.py:289:45: math-constant Replace `3.14` with `math.pi`
- ibis/backends/impala/tests/test_udf.py:289:45: math-constant [*] Replace `3.14` with `math.pi`
+ ibis/backends/postgres/tests/test_functions.py:353:55: math-constant Replace `3.14` with `math.pi`
- ibis/backends/postgres/tests/test_functions.py:353:55: math-constant [*] Replace `3.14` with `math.pi`
+ ibis/backends/postgres/tests/test_functions.py:353:62: math-constant Replace `3.14` with `math.pi`
- ibis/backends/postgres/tests/test_functions.py:353:62: math-constant [*] Replace `3.14` with `math.pi`
+ ibis/backends/risingwave/tests/test_functions.py:184:55: math-constant Replace `3.14` with `math.pi`
- ibis/backends/risingwave/tests/test_functions.py:184:55: math-constant [*] Replace `3.14` with `math.pi`
... 58 additional changes omitted for project

langchain-ai/langchain (+5 -5 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ libs/core/tests/unit_tests/prompts/test_string.py:75:35: math-constant Replace `3.14159` with `math.pi`
- libs/core/tests/unit_tests/prompts/test_string.py:75:35: math-constant [*] Replace `3.14159` with `math.pi`
+ libs/core/tests/unit_tests/utils/test_formatting.py:72:55: math-constant Replace `3.14159` with `math.pi`
- libs/core/tests/unit_tests/utils/test_formatting.py:72:55: math-constant [*] Replace `3.14159` with `math.pi`
+ libs/core/tests/unit_tests/utils/test_utils.py:505:10: math-constant Replace `3.14` with `math.pi`
- libs/core/tests/unit_tests/utils/test_utils.py:505:10: math-constant [*] Replace `3.14` with `math.pi`
+ libs/core/tests/unit_tests/utils/test_utils.py:505:16: math-constant Replace `3.14` with `math.pi`
- libs/core/tests/unit_tests/utils/test_utils.py:505:16: math-constant [*] Replace `3.14` with `math.pi`
+ libs/core/tests/unit_tests/utils/test_utils.py:505:22: math-constant Replace `3.14` with `math.pi`
- libs/core/tests/unit_tests/utils/test_utils.py:505:22: math-constant [*] Replace `3.14` with `math.pi`

prefecthq/prefect (+13 -13 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ src/integrations/prefect-dbt/tests/core/test_orchestrator_per_node.py:1127:43: math-constant Replace `2.71` with `math.e`
- src/integrations/prefect-dbt/tests/core/test_orchestrator_per_node.py:1127:43: math-constant [*] Replace `2.71` with `math.e`
+ src/integrations/prefect-dbt/tests/core/test_orchestrator_per_node.py:1138:71: math-constant Replace `2.71` with `math.e`
- src/integrations/prefect-dbt/tests/core/test_orchestrator_per_node.py:1138:71: math-constant [*] Replace `2.71` with `math.e`
+ src/integrations/prefect-dbt/tests/core/test_orchestrator_per_wave.py:1100:39: math-constant Replace `3.14` with `math.pi`
- src/integrations/prefect-dbt/tests/core/test_orchestrator_per_wave.py:1100:39: math-constant [*] Replace `3.14` with `math.pi`
+ src/integrations/prefect-dbt/tests/core/test_orchestrator_per_wave.py:1112:71: math-constant Replace `3.14` with `math.pi`
- src/integrations/prefect-dbt/tests/core/test_orchestrator_per_wave.py:1112:71: math-constant [*] Replace `3.14` with `math.pi`
+ tests/_sdk/test_renderer.py:50:27: math-constant Replace `3.14` with `math.pi`
- tests/_sdk/test_renderer.py:50:27: math-constant [*] Replace `3.14` with `math.pi`
... 16 additional changes omitted for project

qdrant/qdrant-client (+1 -1 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ tests/conversions/fixtures.py:1626:57: math-constant Replace `2.718` with `math.e`
- tests/conversions/fixtures.py:1626:57: math-constant [*] Replace `2.718` with `math.e`

pytest-dev/pytest (+1 -1 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ testing/typing_checks.py:47:28: math-constant Replace `3.14` with `math.pi`
- testing/typing_checks.py:47:28: math-constant [*] Replace `3.14` with `math.pi`

astropy/astropy (+19 -19 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ astropy/io/ascii/tests/test_read.py:163:24: math-constant Replace `3.14` with `math.pi`
- astropy/io/ascii/tests/test_read.py:163:24: math-constant [*] Replace `3.14` with `math.pi`
+ astropy/io/misc/tests/test_hdf5.py:451:20: math-constant Replace `3.14159` with `math.pi`
- astropy/io/misc/tests/test_hdf5.py:451:20: math-constant [*] Replace `3.14159` with `math.pi`
+ astropy/io/misc/tests/test_hdf5.py:583:20: math-constant Replace `3.14159` with `math.pi`
- astropy/io/misc/tests/test_hdf5.py:583:20: math-constant [*] Replace `3.14159` with `math.pi`
+ astropy/io/misc/tests/test_parquet.py:477:20: math-constant Replace `3.14159` with `math.pi`
- astropy/io/misc/tests/test_parquet.py:477:20: math-constant [*] Replace `3.14159` with `math.pi`
+ astropy/modeling/tests/test_bounding_box.py:201:34: math-constant Replace `3.14` with `math.pi`
- astropy/modeling/tests/test_bounding_box.py:201:34: math-constant [*] Replace `3.14` with `math.pi`
... 28 additional changes omitted for project

... Truncated remaining completed project reports due to GitHub comment length restrictions

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
math-constant 250 125 125 0 0

@ntBre ntBre left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Just a couple of simplification suggestions.

Comment on lines +98 to +107
// The rule matches any literal that rounds to the constant, so `3.14` is reported
// just as `3.141592653589793` is. Substituting the constant only leaves the
// program computing the same values in the latter case; in the former it silently
// replaces the author's number with a different one.
#[expect(
clippy::float_cmp,
reason = "an exact comparison is the point: only a literal that is already the \
same float can be replaced without changing what the program computes"
)]
let is_exact = value == constant.value();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would probably just always mark the fix unsafe. It's not a ton of complexity, but it would still be a little simpler and avoid so much explanation in the docs, and it seems relatively rare to have pasted the exact value of a constant without using the constant, so we'll rarely offer the safe fix anyway.

Comment on lines +29 to +41
/// ## Known problems
/// A literal is matched on its digits alone, so a value that merely happens to
/// begin with the same digits as a mathematical constant is flagged even when it
/// means something else entirely. A measurement that rounds to `3.14`, or a price
/// of `2.718`, is indistinguishable here from an approximation of π or e.
///
/// ## Fix safety
/// This rule's fix is marked as safe only when the literal already denotes exactly
/// the same float as the constant, so that replacing it cannot change a result.
/// Shorter approximations are rewritten under an unsafe fix, because `math.pi` is
/// not equal to `3.14`: a literal that was deliberately chosen — a rounded
/// measurement, a threshold, a test fixture — computes a different value once it
/// becomes a constant.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming we implement my other comment, let's fold this into a much shorter fix safety section. All we really need to say is that the fix is unsafe because it changes the result of calculations.

@ntBre ntBre added rule Implementing or modifying a lint rule preview Related to preview-mode features labels Oct 2, 2026
Per review: drop the exact-literal special case and shorten the fix safety
section.
@DeviousCardi DeviousCardi changed the title [refurb] Only mark math-constant fix safe for exact literals (FURB152) [refurb] Mark math-constant fix as unsafe (FURB152) Oct 6, 2026
@ntBre ntBre changed the title [refurb] Mark math-constant fix as unsafe (FURB152) [refurb] Mark fix unsafe and move to suspicious (FURB152) Oct 6, 2026

@ntBre ntBre left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Comment thread crates/ruff_linter/src/rules/refurb/rules/math_constant.rs Outdated
@ntBre
ntBre enabled auto-merge (squash) October 6, 2026 13:34
@ntBre
ntBre merged commit c5c561c into astral-sh:main Oct 6, 2026
51 checks passed
@DeviousCardi
DeviousCardi deleted the fix/28127-furb152-unsafe branch October 18, 2026 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Related to preview-mode features rule Implementing or modifying a lint rule

Projects

None yet

Development

Successfully merging this pull request may close these issues.

math-constant (FURB152) fix should be considered unsafe

2 participants