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

Issue with type-narrowing inside while loop #3236

Closed
bijij opened this issue Mar 22, 2022 · 3 comments
Closed

Issue with type-narrowing inside while loop #3236

bijij opened this issue Mar 22, 2022 · 3 comments
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working

Comments

@bijij
Copy link

bijij commented Mar 22, 2022

Describe the bug
Pyright seemingly resets type-narrowing in specific circumstances. however adding boilerplate code fixes this?

To Reproduce
See the Screenshots/Code section below.

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots or Code
With the original code:

from typing_extensions import Self
from collections.abc import Generator

class A:
    parent: Self | None

class B:...

def foo(v: A | B | None) -> Generator[A, None, None]:
    reveal_type(v)
    if not isinstance(v, B):
        reveal_type(v)
        while v is not None:
            reveal_type(v)
            yield v
            v = v.parent
            reveal_type(v)

Pyright outputs the following:

| test.py:10:17 - information: Type of "v" is "A | B | None"
| test.py:12:21 - information: Type of "v" is "A | None"
| test.py:14:25 - information: Type of "v" is "A | B" ???
- test.py:15:19 - error: Expression of type "A | B" cannot be assigned to yield type "A"
-   Type "A | B" cannot be assigned to type "A"
-     "B" is incompatible with "A" (reportGeneralTypeIssues)
- test.py:16:19 - error: Cannot access member "parent" for type "B"
-   Member "parent" is unknown (reportGeneralTypeIssues)
| test.py:17:25 - information: Type of "v" is "A | B | None"

Moving the None check inside the loop

from typing_extensions import Self
from collections.abc import Generator

class A:
    parent: Self | None

class B:...

def foo(v: A | B | None) -> Generator[A, None, None]:
    reveal_type(v)
    if not isinstance(v, B):
        reveal_type(v)
        while True:
            if v is None:
                break
            reveal_type(v)
            yield v
            v = v.parent
            reveal_type(v)

Pyright outputs the following

| test.py:10:17 - information: Type of "v" is "A | B | None"
| test.py:12:21 - information: Type of "v" is "A | None"
| test.py:16:25 - information: Type of "v" is "A | B"  ???
- test.py:17:19 - error: Expression of type "A | B" cannot be assigned to yield type "A"
-   Type "A | B" cannot be assigned to type "A"
-     "B" is incompatible with "A" (reportGeneralTypeIssues)
- test.py:18:19 - error: Cannot access member "parent" for type "B"
-   Member "parent" is unknown (reportGeneralTypeIssues)
| test.py:19:25 - information: Type of "v" is "A | B | None"

adding a reveal_type above if v is None:

from typing_extensions import Self, reveal_type
from collections.abc import Generator

class A:
    parent: Self | None

class B:...

def foo(v: A | B | None) -> Generator[A, None, None]:
    reveal_type(v)
    if not isinstance(v, B):
        reveal_type(v)
        while True:
            reveal_type(v)
            if v is None:
                break
            reveal_type(v)
            yield v
            v = v.parent
            reveal_type(v)

Pyright outputs the following

| test.py:10:17 - information: Type of "v" is "A | B | None"
| test.py:12:21 - information: Type of "v" is "A | None"
| test.py:14:25 - information: Type of "v" is "A | None"
| test.py:17:25 - information: Type of "v" is "A"
| test.py:20:25 - information: Type of "v" is "A | None"

why does adding reveal_type here cause this to type-check???

VS Code extension or command-line
pyright 1.1.231

Additional context

mypy, once remoiving reliance on Self since it is currently unsupported for the original snippet outputs the following

test.py:10: note: Revealed type is "Union[test.A, test.B, None]"
test.py:12: note: Revealed type is "Union[test.A, None]"
test.py:14: note: Revealed type is "test.A"
test.py:17: note: Revealed type is "Union[test.A, None]"

which is what I would expect pyright to do.


Interesting tidbit

When using pylance (v2022.3.3-pre.1) moving the position of the cursor and saving will randomly cause the file to pass/fail type-checking. Not sure what to make of that.

Code_HPU5zYAuZ4.mp4

Would it be wise to make an issue on the pylance repo for this?

@erictraut erictraut added the bug Something isn't working label Mar 22, 2022
@erictraut
Copy link
Collaborator

Thanks for the bug report. I'm able to repro the problem. This is a core type checker issue, so no need to file a bug in the pylance repo.

This bug is related to another one that is currently open: #2983. I suspect that both have a similar root cause.

@erictraut
Copy link
Collaborator

This will be addressed in the next release.

@erictraut erictraut added the addressed in next version Issue is fixed and will appear in next published version label Mar 27, 2022
@erictraut
Copy link
Collaborator

This is included in pyright 1.1.233, which I just published. It will also be included in the next release of pylance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants