Skip to content

Use of undefined variable causes unrelated local class to leak scope #13024

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

Open
denballakh opened this issue Jun 26, 2022 · 4 comments
Open
Labels
bug mypy got something wrong topic-variable-scope

Comments

@denballakh
Copy link
Contributor

Bug Report
I noticed a strange behavior and inconsistency when first argument of typing_extensions.assert_type is not defined.

To Reproduce
First case:

## a.py
def f() -> None:
    from typing_extensions import assert_type

    class X:
        pass

    # x = X() # note this line is commented out
    assert_type(x, X)


## b.py
from a import X
reveal_type(X)
# Revealed type is "def () -> a.X@5"

Second case:

## a.py
def f() -> None:
    from typing_extensions import assert_type

    class X:
        pass

    x = X() # note this line is not commented out
    assert_type(x, X)


## b.py
from a import X
reveal_type(X)
# Module "a" has no attribute "X"
# Revealed type is "Any"

To reproduce run mypy b.py.
This snippets differ by only one line x = X(), but mypy behavior is very different. It gives me module has no attribute error, or gives no error.

Expected Behavior
I expect to get the Module "a" has no attribute "X" error in both cases

My Environment

  • mypy 0.961 (compiled: no)
  • no command-line flags, no config file
  • CPython 3.10.4
  • Windows 10
@denballakh denballakh added the bug mypy got something wrong label Jun 26, 2022
@denballakh
Copy link
Contributor Author

Another reproducer:

## a.py
def f() -> None:
    from typing_extensions import assert_type

    # first case: X is importable from this file
    class X:
        a: A # error: name "A" is not defined

    # second case: X is **not** importable from this file
    # class X:
    #     pass

    x = X()
    assert_type(x, X)


## b.py
from a import X
reveal_type(X)

# in this file i get:
# in first case:
# note: Revealed type is "def () -> a.X@5"

# in second case:
# error:Module "a" has no attribute "X"
# note: Revealed type is "Any"

@hauntsaninja
Copy link
Collaborator

hauntsaninja commented Jun 26, 2022

Interesting, thanks for finding this. Note that it also repros with any use of x, not just assert_type

E.g.:

a.py

def f() -> None:
    class X:
        ...
    x

b.py

from a import X
reveal_type(X)

@hauntsaninja hauntsaninja changed the title typing_extensions.assert_type defines variable Use of undefined variable causes unrelated variable to leak scope Jun 26, 2022
@hauntsaninja hauntsaninja changed the title Use of undefined variable causes unrelated variable to leak scope Use of undefined variable causes unrelated local class to leak scope Jun 26, 2022
@denballakh
Copy link
Contributor Author

denballakh commented Jun 26, 2022

Other reproducer, but without using undefined variables:

## a.py
from __future__ import annotations
def f() -> None:
    class X:
        z: Z # i guess, mypy thinks Z isnt defined and leaks all classes into global scope, but there are no errors
    class Z: ...
    a: int
    t: type[X]

## b.py
from a import X, Z, a, t
# Module "a" has no attribute "a" # ok
# Module "a" has no attribute "t" # ok
reveal_type(X)
# Revealed type is "def () -> a.X@3" # not ok
reveal_type(Z)
# Revealed type is "def () -> a.Z@6" # not ok

Note that it leaks only classes, not variables.
If i change order of definition of X and Z everything works as expected.

@hauntsaninja
Copy link
Collaborator

hauntsaninja commented Jun 26, 2022

Took a look at what's going on here, looks like there's some pretty cursed stuff happening in

def prepare_class_def(self, defn: ClassDef, info: Optional[TypeInfo] = None) -> None:

Also see #6422

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-variable-scope
Projects
None yet
Development

No branches or pull requests

3 participants