Skip to content

[flake8-type-checking] Avoid strict behavior when future-annotations are enabled (TC001, TC002, TC003)#25035

Merged
ntBre merged 2 commits into
astral-sh:mainfrom
shivamtiwari3:fix/issue-23185-future-annotations-respects-strict
May 15, 2026
Merged

[flake8-type-checking] Avoid strict behavior when future-annotations are enabled (TC001, TC002, TC003)#25035
ntBre merged 2 commits into
astral-sh:mainfrom
shivamtiwari3:fix/issue-23185-future-annotations-respects-strict

Conversation

@shivamtiwari3
Copy link
Copy Markdown
Contributor

Summary

Fixes #23185.

Enabling lint.future-annotations = true was silently flipping TC001/TC002/TC003 into strict-mode behaviour, even when lint.flake8-type-checking.strict = false. The two settings are orthogonal — the former controls whether from __future__ import annotations is auto-inserted as a fix; the latter controls whether typing-only imports whose module is already imported at runtime should be flagged.

Root cause

In crates/ruff_linter/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs, the skip condition for implicit runtime imports read:

if !checker.settings().future_annotations
    && !checker.settings().flake8_type_checking.strict
    && runtime_imports.iter().any(|import| is_implicit_import(binding, import))
{
    continue;
}

This required both !future_annotations and !strict to be true. The moment a user opted into future_annotations = true, the first clause became false and the entire skip was bypassed — making the rules behave as if strict = true, regardless of the user's explicit strict = false setting.

This also matches the direction @ntBre suggested on the issue:

This does sound like a bug to me. I think I may have gotten this logic wrong, and it should be an or instead of and

Solution

Drop the !future_annotations clause. Only !strict should gate whether implicit runtime imports are skipped. The future_annotations setting is handled elsewhere (as part of the fix-generation path for adding the __future__ import); it has no bearing on which imports are flagged.

Testing

  • New regression test in crates/ruff_linter/src/rules/flake8_type_checking/mod.rs: future_annotations_respects_non_strict_mode, backed by a new fixture crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC001-3_future_strict.py. The test pins the exact scenario from the issue (future_annotations=true, strict=false, typing-only import whose module has a runtime-used sibling) and asserts zero TC001/TC002/TC003 diagnostics are produced — a result that would have been non-zero prior to this patch.
  • All 92 pre-existing flake8_type_checking lib tests still pass locally; the add_future_import snapshot tests (which exercise the future_annotations = true path) also pass unchanged, confirming the fix does not regress the auto-insert-__future__-import behaviour.
    • cargo test --lib flake8_type_checking → 92 passed, 0 failed
    • cargo test --lib add_future_import → 12 passed, 0 failed

Checklist

  • Fixes the root cause (not just the symptom)
  • New test covers the exact failing scenario from the issue
  • All existing tests pass
  • No unrelated changes
  • Code style matches project conventions
  • Followed CONTRIBUTING.md

…02/TC003 (fixes astral-sh#23185)

Root cause: the skip condition for implicit runtime imports in
typing_only_runtime_import required BOTH !future_annotations AND !strict
to be true. When a user enabled future_annotations=true without enabling
strict mode, the first clause short-circuited the skip, causing
TC001/TC002/TC003 to flag imports that have valid runtime siblings --
silently flipping the rules into strict-mode behavior.

Fix: gate the skip on the strict setting only. future_annotations is
orthogonal -- it controls whether 'from __future__ import annotations'
is auto-inserted as part of the fix, not whether runtime-shadowed
imports are flagged.

Matches the intent stated in the docs and the direction suggested by
the maintainer on the issue.
@astral-sh-bot astral-sh-bot Bot requested a review from ntBre May 8, 2026 01:44
Copy link
Copy Markdown
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

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

I had a couple of nits, but this looks right to me, thanks!

Comment thread crates/ruff_linter/src/rules/flake8_type_checking/mod.rs Outdated
Comment thread crates/ruff_linter/src/rules/flake8_type_checking/mod.rs Outdated
@ntBre ntBre added the bug Something isn't working label May 13, 2026
…re/test docstrings

Per @ntBre's review on astral-sh#25035:
- Restore the simpler pre-astral-sh#19100 comment in typing_only_runtime_import.rs
- Use assert_diagnostics! for the regression test so future changes
  produce a snapshot diff instead of a hard assert failure
- Drop the duplicated explanation from the test docstring and fixture
  comments; the test name + fixture header are sufficient context
@shivamtiwari3
Copy link
Copy Markdown
Contributor Author

Pushed 0cda6065 addressing all three review nits:

Full flake8_type_checking suite (92 tests) still passes locally. PTAL.

Copy link
Copy Markdown
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

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

Thanks!

@ntBre ntBre changed the title Fix: future_annotations incorrectly enables strict-mode behavior for TC001/TC002/TC003 (fixes #23185) [flake8-type-checking] Avoid strict behavior when future-annotations are enabled (TC001, TC002, TC003) May 15, 2026
@ntBre ntBre merged commit 5ace2a8 into astral-sh:main May 15, 2026
44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lint.future-annotations = true causes TC001/TC002/TC003 to act like lint.flake8-type-checking.strict

2 participants