Skip to content

[flake8-pyi] Mark PYI061 fixes unsafe in Python files#26533

Merged
ntBre merged 4 commits into
astral-sh:mainfrom
jesco-absolut:fix-20729-pyi061-safety
Jul 8, 2026
Merged

[flake8-pyi] Mark PYI061 fixes unsafe in Python files#26533
ntBre merged 4 commits into
astral-sh:mainfrom
jesco-absolut:fix-20729-pyi061-safety

Conversation

@jesco-absolut

Copy link
Copy Markdown
Contributor

Summary

Closes #20729.

This marks PYI061 fixes as unsafe in non-stub Python files.

The fix can rewrite Literal["foo", "bar", None] into Literal["foo", "bar"] | None. That is equivalent for type checkers, but it is not fully equivalent at runtime: code that introspects annotations with typing.get_args() observes a different structure after the rewrite.

Stub files keep the previous behavior, since they are not runtime Python files: fixes are still safe unless comments are removed.

I also added a regression case to the existing PYI061.py fixture. While updating the snapshots, this also removes trailing whitespace from the affected diagnostic messages.

Test Plan

Reproduced the issue before the change:

cargo run -p ruff -- check /tmp/pyi061_repro.py --select PYI061 --fix --diff --no-cache --isolated

Verified that after the change, the fix is no longer applied by default and is only available with --unsafe-fixes.

Verified that the fix is still available with unsafe fixes enabled:

cargo run -p ruff -- check /tmp/pyi061_repro.py --select PYI061 --fix --diff --unsafe-fixes --no-cache --isolated

Ran the targeted snapshot tests:

CARGO_PROFILE_DEV_OPT_LEVEL=1 INSTA_FORCE_PASS=1 INSTA_UPDATE=always CARGO_PROFILE_DEV_DEBUG="line-tables-only" MDTEST_UPDATE_SNAPSHOTS=1 cargo test -p ruff_linter rule_redundantnoneliteral_path_new_pyi061 -- --nocapture

Also ran:

cargo dev generate-all
uv run --only-group dev --locked prek run --files crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.py crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_none_literal.rs crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI061_PYI061.py.snap crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI061_PYI061.pyi.snap crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.py.snap crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__py38_PYI061_PYI061.pyi.snap
git diff --check

@astral-sh-bot
astral-sh-bot Bot requested a review from ntBre July 4, 2026 05:38
@ntBre ntBre added the fixes Related to suggested fixes for violations label Jul 7, 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.

Thank you, this makes sense to me! I just had one suggestion/request about the test location.

Comment thread crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI061.py Outdated
@ntBre ntBre changed the title Mark PYI061 fixes unsafe in Python files [flake8-pyi] Mark PYI061 fixes unsafe in Python files Jul 7, 2026
@astral-sh-bot

astral-sh-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

ruff-ecosystem results

Linter (stable)

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

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

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

+ airflow-core/src/airflow/serialization/definitions/dag.py:999:36: PYI061 Use `Literal[...] | None` rather than `Literal[None, ...]`
- airflow-core/src/airflow/serialization/definitions/dag.py:999:36: PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]` 

latchbio/latch (+1 -1 violations, +0 -0 fixes)

+ src/latch/types/metadata.py:496:45: PYI061 Use `Optional[Literal[...]]` rather than `Literal[None, ...]`
- src/latch/types/metadata.py:496:45: PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` 

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
PYI061 4 2 2 0 0

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+4 -4 violations, +0 -0 fixes in 3 projects; 53 projects unchanged)

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

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

+ airflow-core/src/airflow/serialization/definitions/dag.py:999:36: redundant-none-literal Use `Literal[...] | None` rather than `Literal[None, ...]`
- airflow-core/src/airflow/serialization/definitions/dag.py:999:36: redundant-none-literal [*] Use `Literal[...] | None` rather than `Literal[None, ...]` 

latchbio/latch (+1 -1 violations, +0 -0 fixes)

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

+ src/latch/types/metadata.py:496:45: redundant-none-literal Use `Optional[Literal[...]]` rather than `Literal[None, ...]`
- src/latch/types/metadata.py:496:45: redundant-none-literal [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]` 

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

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

+ src/prefect/tasks.py:1882:19: redundant-none-literal Use `None` rather than `Literal[None]`
- src/prefect/tasks.py:1882:19: redundant-none-literal [*] Use `None` rather than `Literal[None]`
+ src/prefect/tasks.py:1919:19: redundant-none-literal Use `None` rather than `Literal[None]`
- src/prefect/tasks.py:1919:19: redundant-none-literal [*] Use `None` rather than `Literal[None]`

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
redundant-none-literal 8 4 4 0 0

@ntBre
ntBre merged commit 29f9fba into astral-sh:main Jul 8, 2026
47 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fixes Related to suggested fixes for violations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix for redundant-none-literal (PYI061) is marked as safe but should not be

2 participants