-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
Bug Report
In mypy 1.17.0, type variables for arguments which are defined as the union of a generic TypeVar
instance or type are not correctly type-narrowed.
To Reproduce
cf https://mypy-play.net/?mypy=latest&python=3.12&gist=630030506910825d5db3d022cdd4198f
from typing import TypeVar
MyType = TypeVar("MyType")
class HelloMixin:
a: str
def hello(world: MyType | type[MyType]) -> HelloMixin | None:
world = world if isinstance(world, type) else type(world)
reveal_type(world)
if not issubclass(world, HelloMixin):
return None
reveal_type(world)
res = world()
reveal_type(res)
reveal_type(res.a)
return res
Expected Behavior
In mypy 1.16.1, type checking succeeds, and the variables are correctly type-narrowed, i.e. after the if not issubclass(world, HelloMixin):
, world
is correctly typed as type[subclass_narrowing.HelloMixin]
:
% /tmp/core/venv/bin/mypy --version
mypy 1.16.1 (compiled: yes)
% /tmp/core/venv/bin/mypy /tmp/subclass_narrowing.py
/tmp/subclass_narrowing.py: note: In function "hello":
/tmp/subclass_narrowing.py:13:17: note: Revealed type is "builtins.type"
/tmp/subclass_narrowing.py:17:17: note: Revealed type is "type[subclass_narrowing.HelloMixin]"
/tmp/subclass_narrowing.py:19:17: note: Revealed type is "subclass_narrowing.HelloMixin"
/tmp/subclass_narrowing.py:20:17: note: Revealed type is "builtins.str"
Success: no issues found in 1 source file
Actual Behavior
In mypy 1.17.0:
% ./venv/bin/mypy --version
mypy 1.17.0 (compiled: yes)
% ./venv/bin/mypy /tmp/subclass_narrowing.py
/tmp/subclass_narrowing.py: note: In function "hello":
/tmp/subclass_narrowing.py:13:17: note: Revealed type is "MyType`-1 | type[MyType`-1]"
/tmp/subclass_narrowing.py:17:17: note: Revealed type is "MyType`-1 | type[MyType`-1]"
/tmp/subclass_narrowing.py:19:17: note: Revealed type is "Any | MyType`-1"
/tmp/subclass_narrowing.py:20:17: error: Item "object" of "Any | MyType" has no attribute "a" [union-attr]
reveal_type(res.a)
^~~~~
/tmp/subclass_narrowing.py:20:17: note: See https://mypy.rtfd.io/en/stable/_refs.html#code-union-attr for more info
/tmp/subclass_narrowing.py:20:17: note: Revealed type is "Any"
/tmp/subclass_narrowing.py:21:12: error: Incompatible return value type (got "Any | MyType", expected "HelloMixin | None") [return-value]
return res
^~~
Found 2 errors in 1 file (checked 1 source file)
Note the revealed type of the world
argument: in 1.16.1 it starts off as just builtins.type
, while 1.17 more precisely types it as MyType-1 | type[MyType-1]
before the issubclass()
check.
A regular world: type[MyType]
does not exhibit this behaviour, nor does concrete types (e.g. using Base | type[Base]
, with HelloMixin
as a Base
subclass). This only happens when using an union, world: type[MyType]
does not exhibit the bug.
I'm not sure where the problem comes from exactly, but the first reveal_type(world)
shows us that the line making sure it's always a type already fails to be narrowed to type[MyType-1]
. Using a dedicated variable (i.e. world_type = world if isinstance(world, type) else type(world)
) still fails as well.
Your Environment
- Mypy version used: 1.17
- Mypy command-line flags:
- Mypy configuration options from
mypy.ini
(and other config files): - Python version used: 3.12.10