Skip to content
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

UP007 false negative with quoted type #5706

Closed
dorschw opened this issue Jul 12, 2023 · 2 comments · Fixed by #5725
Closed

UP007 false negative with quoted type #5706

dorschw opened this issue Jul 12, 2023 · 2 comments · Fixed by #5725
Assignees
Labels
bug Something isn't working

Comments

@dorschw
Copy link

dorschw commented Jul 12, 2023

UP007 won't recognize cases like

from typing import Optional


def foo(_: Optional["myclass"]):
    pass

While it recognizes

from typing import Optional


def foo(_: Optional[myclass]):
    pass

reproduces with 0.0.277 and on the playground

@charliermarsh
Copy link
Member

I think this follows pyupgrade’s behavior, but I need to look into if that’s true, and why.

@charliermarsh charliermarsh added bug Something isn't working needs-decision Awaiting a decision from a maintainer labels Jul 12, 2023
@charliermarsh
Copy link
Member

I think this is the correct behavior (in some cases). E.g., this fails for me on 3.11:

from typing import Optional


def foo(_:  "myclass" | None):
    pass

With:

Traceback (most recent call last):
  File "/Users/crmarsh/workspace/ruff/foo.py", line 4, in <module>
    def foo(_:  "myclass" | None):
                ~~~~~~~~~~^~~~~~
TypeError: unsupported operand type(s) for |: 'str' and 'NoneType'

However, it's sometimes okay, e.g. this is fine:

from typing import Optional

class MyClass:
    pass


def foo():
    x: "MyClass" | None = MyClass()

foo()

But this is not:

from typing import Optional

class MyClass:
    pass


def foo():
    x: "MyClass" | None = MyClass()

    def bar(x: "MyClass" | None):
        pass

foo()

It has to do with when Python evaluates annotations at runtime (e.g., any parameters to functions need to be evaluated at runtime because they get added to __annotations__). We can support this in more cases but it's not safe to do everywhere.

@charliermarsh charliermarsh self-assigned this Jul 12, 2023
@charliermarsh charliermarsh removed the needs-decision Awaiting a decision from a maintainer label Jul 12, 2023
charliermarsh added a commit that referenced this issue Jul 13, 2023
## Summary

Python doesn't allow `"Foo" | None` if the annotation will be evaluated
at runtime (see the comments in the PR, or the semantic model
documentation for more on what this means and when it is true), but it
_does_ allow it if the annotation is typing-only.

This, for example, is invalid, as Python will evaluate `"Foo" | None` at
runtime in order to
populate the function's `__annotations__`:

```python
def f(x: "Foo" | None): ...
```

This, however, is valid:

```python
def f():
    x: "Foo" | None
```

As is this:

```python
from __future__ import annotations

def f(x: "Foo" | None): ...
```

Closes #5706.
konstin pushed a commit that referenced this issue Jul 19, 2023
## Summary

Python doesn't allow `"Foo" | None` if the annotation will be evaluated
at runtime (see the comments in the PR, or the semantic model
documentation for more on what this means and when it is true), but it
_does_ allow it if the annotation is typing-only.

This, for example, is invalid, as Python will evaluate `"Foo" | None` at
runtime in order to
populate the function's `__annotations__`:

```python
def f(x: "Foo" | None): ...
```

This, however, is valid:

```python
def f():
    x: "Foo" | None
```

As is this:

```python
from __future__ import annotations

def f(x: "Foo" | None): ...
```

Closes #5706.
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 a pull request may close this issue.

2 participants