-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TCH004: false positive for import that is used only in type hints #7214
Comments
Oh, this is half-wrong. I need to include I'm not sure that the auto-fix is correct here; the auto-fix moves the The correct fix in this case is to include |
Yeah, unfortunately Python requires that type annotations on function signatures are available at runtime. (This isn't true of all annotations -- for example, annotated assignments outside of the top-level are not evaluated at runtime IIRC.) The current fix is limited to moving imports as it attempts to avoid changing the semantics of the code. We've explored supporting automatic quoting for annotations (#6001), which would let us handle this case without adding a (If you do want to use |
The main problem here is that the auto-fix suggests moving the imports out of the `if TYPE_CHECKING`, but this is not correct because the imported types are themselves defined in a `if TYPE_CHECKING` block (and cannot be imported when `TYPE_CHECKING == false`.
I do agree that including `from __future__ import annotations` might have unintended consequences, but the current fix simply generates broken code.
Perhaps both fixes can be offered as LSP code actions but none applied automatically with `--fix`, given that which one is correct is very contextual (unless you think that it’s feasible to determine which of both fixes is correct in each scenario).
|
We can't know that the imported types are only available when type checking (without adding multifile analysis) and that seems like a relatively rare case. Perhaps it's worth opening a bug report at Sentry? I'm curious why they made that design decision. We are already using the "suggested" applicability for this fix so once #4185 lands we will not apply this when |
I have an example that shows another false positive with TCH004:
[tool.ruff]
extend-select = ["TCH"]
line-length = 120
target-version = "py311"
import abc
from typing import Generic
from typing import TypeVar
T = TypeVar("T")
class MyBaseClass(abc.ABC, Generic[T]):
pass
from __future__ import annotations
from typing import TYPE_CHECKING
from .base import MyBaseClass
if TYPE_CHECKING:
from collections.abc import Iterator
class MyConcreteClass(MyBaseClass[list[Iterator]]):
def __init__(self, items: list[Iterator]):
self.items = items Error: concrete.py:8:33: TCH004 [*] Move import `collections.abc.Iterator` out of type-checking block. Import is used for more than type hinting.
Found 1 error.
[*] 1 potentially fixable with the --fix option. Tbh, I'm not sure if this is a false positive or is a correct error. |
@aqeelat - Thanks for the clear write-up. I believe that error is correct, since |
Rebase of #5119 authored by @evanrittenhouse with additional refinements. ## Changes - Adds `--unsafe-fixes` / `--no-unsafe-fixes` flags to `ruff check` - Violations with unsafe fixes are not shown as fixable unless opted-in - Fix applicability is respected now - `Applicability::Never` fixes are no longer applied - `Applicability::Sometimes` fixes require opt-in - `Applicability::Always` fixes are unchanged - Hints for availability of `--unsafe-fixes` added to `ruff check` output ## Examples Check hints at hidden unsafe fixes ``` ❯ ruff check example.py --no-cache --select F601,W292 example.py:1:14: F601 Dictionary key literal `'a'` repeated example.py:2:15: W292 [*] No newline at end of file Found 2 errors. [*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option). ``` We could add an indicator for which violations have hidden fixes in the future. Check treats unsafe fixes as applicable with opt-in ``` ❯ ruff check example.py --no-cache --select F601,W292 --unsafe-fixes example.py:1:14: F601 [*] Dictionary key literal `'a'` repeated example.py:2:15: W292 [*] No newline at end of file Found 2 errors. [*] 2 fixable with the --fix option. ``` Also can be enabled in the config file ``` ❯ cat ruff.toml unsafe-fixes = true ``` And opted-out per invocation ``` ❯ ruff check example.py --no-cache --select F601,W292 --no-unsafe-fixes example.py:1:14: F601 Dictionary key literal `'a'` repeated example.py:2:15: W292 [*] No newline at end of file Found 2 errors. [*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option). ``` Diff does not include unsafe fixes ``` ❯ ruff check example.py --no-cache --select F601,W292 --diff --- example.py +++ example.py @@ -1,2 +1,2 @@ x = {'a': 1, 'a': 1} -print(('foo')) +print(('foo')) \ No newline at end of file Would fix 1 error. ``` Unless there is opt-in ``` ❯ ruff check example.py --no-cache --select F601,W292 --diff --unsafe-fixes --- example.py +++ example.py @@ -1,2 +1,2 @@ -x = {'a': 1} -print(('foo')) +x = {'a': 1, 'a': 1} +print(('foo')) \ No newline at end of file Would fix 2 errors. ``` #7790 will improve the diff messages following this pull request Similarly, `--fix` and `--fix-only` require the `--unsafe-fixes` flag to apply unsafe fixes. ## Related Replaces #5119 Closes #4185 Closes #7214 Closes #4845 Closes #3863 Addresses #6835 Addresses #7019 Needs follow-up #6962 Needs follow-up #4845 Needs follow-up #7436 Needs follow-up #7025 Needs follow-up #6434 Follow-up #7790 Follow-up #7792 --------- Co-authored-by: Evan Rittenhouse <[email protected]>
Rebase of #5119 authored by @evanrittenhouse with additional refinements. ## Changes - Adds `--unsafe-fixes` / `--no-unsafe-fixes` flags to `ruff check` - Violations with unsafe fixes are not shown as fixable unless opted-in - Fix applicability is respected now - `Applicability::Never` fixes are no longer applied - `Applicability::Sometimes` fixes require opt-in - `Applicability::Always` fixes are unchanged - Hints for availability of `--unsafe-fixes` added to `ruff check` output ## Examples Check hints at hidden unsafe fixes ``` ❯ ruff check example.py --no-cache --select F601,W292 example.py:1:14: F601 Dictionary key literal `'a'` repeated example.py:2:15: W292 [*] No newline at end of file Found 2 errors. [*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option). ``` We could add an indicator for which violations have hidden fixes in the future. Check treats unsafe fixes as applicable with opt-in ``` ❯ ruff check example.py --no-cache --select F601,W292 --unsafe-fixes example.py:1:14: F601 [*] Dictionary key literal `'a'` repeated example.py:2:15: W292 [*] No newline at end of file Found 2 errors. [*] 2 fixable with the --fix option. ``` Also can be enabled in the config file ``` ❯ cat ruff.toml unsafe-fixes = true ``` And opted-out per invocation ``` ❯ ruff check example.py --no-cache --select F601,W292 --no-unsafe-fixes example.py:1:14: F601 Dictionary key literal `'a'` repeated example.py:2:15: W292 [*] No newline at end of file Found 2 errors. [*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option). ``` Diff does not include unsafe fixes ``` ❯ ruff check example.py --no-cache --select F601,W292 --diff --- example.py +++ example.py @@ -1,2 +1,2 @@ x = {'a': 1, 'a': 1} -print(('foo')) +print(('foo')) \ No newline at end of file Would fix 1 error. ``` Unless there is opt-in ``` ❯ ruff check example.py --no-cache --select F601,W292 --diff --unsafe-fixes --- example.py +++ example.py @@ -1,2 +1,2 @@ -x = {'a': 1} -print(('foo')) +x = {'a': 1, 'a': 1} +print(('foo')) \ No newline at end of file Would fix 2 errors. ``` #7790 will improve the diff messages following this pull request Similarly, `--fix` and `--fix-only` require the `--unsafe-fixes` flag to apply unsafe fixes. ## Related Replaces #5119 Closes #4185 Closes #7214 Closes #4845 Closes #3863 Addresses #6835 Addresses #7019 Needs follow-up #6962 Needs follow-up #4845 Needs follow-up #7436 Needs follow-up #7025 Needs follow-up #6434 Follow-up #7790 Follow-up #7792 --------- Co-authored-by: Evan Rittenhouse <[email protected]>
My
test.py
:Steps to reproduce:
In this particular case,
sentry_sdk
only exports typesif TYPE_CHECKING
, so I cannot move this out of the type checking block.The text was updated successfully, but these errors were encountered: