Open binishkaspar opened 1 year ago
Mypy is narrowing the type of typ
to type[DataclassInstance]
because the is_dataclass
function which was recently changed to use a TypeGuard
. This is why the behavior changed with recent versions of mypy. Here's the relevant overload from dataclasses.pyi
:
def is_dataclass(obj: type) -> TypeGuard[type[DataclassInstance]]: ...
i think that narrowing from type[T]
to type[DataclassInstance]
is problematic becuase DataclassInstance
is a Protocol
. type[DataclassInstance]
is not always a subtype of type[T]
[^1].
would this be solved if type narrowing used intersections (https://github.com/python/typing/issues/213)?
[^1]: depending on the bound on T
. if T
is unbound then type[DataclassInstance]
is a subtype of type[T]
, but if we had a bound T = TypeVar("T", bound=A)
and A
wasn't a subtype of DataclassInstance
, then type[DataclassInstance]
would legitimately not be a subtype of type[T]
.
here's a small reproducer that isn't tied to the recent typeshed changes:
@runtime_checkable
class _HasFoo(Protocol):
# some structural type that is third-party private and not available for users to use in annotations.
@classmethod
def foo(cls) -> None:
...
def class_has_foo(typ: type, /) -> TypeGuard[type[_HasFoo]]:
# some TypeGuard for type[it] that is third-party public.
return issubclass(typ, _HasFoo)
def construct(typ: type[T], /) -> T:
if not class_has_foo(typ):
raise TypeError()
return typ()
(https://mypy-play.net/?mypy=1.1.1&python=3.11&gist=e1d8c7379c14222d2ec5f2e91eca4f62)
pyright and pyre error with this too.
would this be solved if type narrowing used intersections
Yes, intersections would solve this.
pyright and pyre error with this too.
I just checked in a fix for pyright that addresses this issue. Pyright has the concept of conditional types, which is effectively an internal intersection between a TypeVar and another type.
I think for now I will introduce a new function guardsafe_is_dataclass
(may be need a better name) which returns bool without using TypeGuard
from dataclasses import dataclass, is_dataclass
from typing import TypeVar, Any, Type, cast
@dataclass
class A:
a: int
T = TypeVar('T')
def guardsafe_is_dataclass(typ: Type[T]) -> bool:
return is_dataclass(typ)
def parse(typ: Type[T], raw: dict[str, Any]) -> T:
if not guardsafe_is_dataclass(typ):
raise Exception('Unsupported type')
parsed = typ(**raw) # type: ignore[call-arg]
return parsed
parse(A, {'a': 2})
Can we expect this to be fixed?
Bug Report In the following code snippet, mypy incorrectly infers the type of typ as _typeshed.DataclassInstance. This issue was not present in previous versions of mypy
To Reproduce
Expected Behavior
There should be no type error
Actual Behavior
Incompatible return value type (got "DataclassInstance", expected "T") \[return-value\]
Your Environment