-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
regression: ruff deletes imports; successive checks fail due to missing import #14698
Comments
Also not an issue on |
Produced broken results. See: astral-sh/ruff#14698
Looks like this regression was caused by #14615 If you remove the |
Is this incorrect? The spec says that "Functions with the |
I think what it means is that we should treat its parameters (and return type) as from typing import no_type_check
from library import Foo, Bar # Magic classes
@no_type_check
def f(v: Foo()) -> Bar(): ...
def frobnicate(f): # More magic
...; inspect.signature(f); ... |
What the typing spec says is irrelevant in this case, since the annotations will still be stored by the compiler and will be accessible at runtime. So the annotations should only really be ignored as far as type checkers are concerned. Now, why you would annotate a function that is marked with |
I don't really agree, but from a practical standpoint, is does seem wrong to be removing imports here. I'm not sure where to draw the line though. Should this raise a lint error? That was the initiating use-case.
|
Yes, absolutely it should. You could maybe argue that it shouldn't if you use |
Does your opinion change given:
|
This is functionally equivalent to the |
While annotations were originally intended as a general purpose feature, the typing use-case is now so prevalent, that you can't really expect to use them for anything else without using Using |
In this particular case, some of the variables used in this function are runtime-generated types, so there is no correct annotation for them. The type hints are hints for developers, so they understand what types these functions take as parameters. These hints also get rendered into generated documentation. |
We should align Ruff's behavior with what other type checkers do for best compatibility. Pyright does not raise any error for the example given by @charliermarsh (playground)
which matches my expectations after reading the specification. But we should definitely avoid removing imports if they're referenced from functions annotated with |
@MichaReiser Ruff in many ways captures much more detailed semantic state when it comes to things other than types compared to other type checkers. As far as I am aware all other type checkers currently ignore runtime/typing context, as in they consider everything to be in the same context. A lot of the lint rules rely on statically inferrable behavior based on the object model, rather than the type system. Type checkers typically rely on linters to detect some of these other issues. As such I don't think it is sufficient to look at what other type checkers do here. You're hiding a statically detectable But rereading the docs, I've come around on the case where forward references are used either implicitly or explicitly. In order to avoid bad interactions with other rules I would still recommend treating them like normal forward references, just with the caveat, that no F821 will be emitted for missing bindings. I.e. a narrow exception to the rule, rather than changing the state of the semantic model by not visiting those nodes at all. The additional state for making the more narrow exception is already being tracked through the Edit: It might also be feasible to switch from skipping to calling from typing import no_type_check, TYPE_CHECKING
if TYPE_CHECKING:
from foo import Foo
@no_type_check
def bar(a: list["Foo"]) -> None: ... So it's probably not really a good idea to do that either, since you're just changing where the problem may occur. There's similar kinds of issues in |
It's not clear to me whether the discussion thus far has accounted for the fact that the imports which are deleted are inside of a I would personally find it a bit surprising to see imports which cannot be resolved at runtime due to being imported under If they're not used for typing, and they're not used for not-typing, then semantically speaking, what precisely are they used for? |
@Daverball I don't think it's important whether Ruff's model is more or less complicated than that of type checkers. What's important is that Ruff's behavior matches users' expectations and, to some extent, the typing spec. The typing spec isn't very clear about most details when it comes to
That's true, but that's the decorator's intent. It's a very broad |
That's only true as far as type errors are concerned. A So I agree that the decorator's intent is to silence any errors relating to the validity of those annotations as types, but annotations are a general purpose construct and they can store arbitrary expressions. It just so happens that that space has mostly been taken over by type hints. So semantically a name lookup still occurs if there are no forward references, so removing that fact from the semantic model is a bug, plain and simple. The case with forward references is more subtle. Implicit forward references look like regular code, so I think people's expectation is that the load is still simulated, even if it may never happen at runtime. If you want to put an actual plain string into the annotation, then use a string, don't abuse an implicit forward reference for that. With explicit forward references I'm fine with skipping the load, but I think it's still more robust to do it anyways and just silence any errors related to failed name lookups (or invalid type expressions). That preserves the annotations as pure documentation without enforcement use-case. |
I get the feeling that we're saying the same but are talking past each other :)
|
We should probably revert this change and redo the implementation |
Edit: For some reason Eli's comment is rendered at the bottom of this issue for me, so I apologize if this confusion had already been cleared up in the meantime. @eli-schwartz This was addressed implicitly through discussion about the desired semantics of In the given example the annotations are still used for documentation. If you remove the imports entirely, you now would have to guess what these symbols mean. That's counter-intuitive, especially with an implicit forward reference introduced through You generally probably wouldn't want ruff to destroy an explicit reference, just because there's a chance it will never be used. An explicit reference is always better than no reference at all, since it makes the code easier to understand. |
Running
ruff check --force-exclude --fix --exit-non-zero-on-fix
on my repository deletes some imports that are actually used.The next time that I run
ruff check .
, it fails because of missing imports. The missing imports are the ones deleted by ruff.Reproduction steps
Observed result
ruff
deletes imports which are in theif TYPE_CHECKING
block:Running
ruff check
now fails:Expected results
No change; ruff should not delete imports which are being used.
Additional details
ruff 0.8.1
from PyPIThe text was updated successfully, but these errors were encountered: