Skip to content

Commit b84cda9

Browse files
committed
Fix types of inherited attributes in generic dataclasses (#12656)
Fixes #12633.
1 parent bf6b02d commit b84cda9

File tree

2 files changed

+59
-5
lines changed

2 files changed

+59
-5
lines changed

mypy/plugins/dataclasses.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
get_proper_type, AnyType, TypeOfAny,
2020
)
2121
from mypy.server.trigger import make_wildcard_trigger
22+
from mypy.state import state
2223

2324
# The set of decorators that generate dataclasses.
2425
dataclass_makers: Final = {
@@ -101,10 +102,8 @@ def deserialize(
101102
def expand_typevar_from_subtype(self, sub_type: TypeInfo) -> None:
102103
"""Expands type vars in the context of a subtype when an attribute is inherited
103104
from a generic super type."""
104-
if not isinstance(self.type, TypeVarType):
105-
return
106-
107-
self.type = map_type_from_supertype(self.type, sub_type, self.info)
105+
if self.type is not None:
106+
self.type = map_type_from_supertype(self.type, sub_type, self.info)
108107

109108

110109
class DataclassTransformer:
@@ -402,7 +401,11 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]:
402401
name: str = data["name"]
403402
if name not in known_attrs:
404403
attr = DataclassAttribute.deserialize(info, data, ctx.api)
405-
attr.expand_typevar_from_subtype(ctx.cls.info)
404+
# TODO: We shouldn't be performing type operations during the main
405+
# semantic analysis pass, since some TypeInfo attributes might
406+
# still be in flux. This should be performed in a later phase.
407+
with state.strict_optional_set(ctx.api.options.strict_optional):
408+
attr.expand_typevar_from_subtype(ctx.cls.info)
406409
known_attrs.add(name)
407410
super_attrs.append(attr)
408411
elif all_attrs:

test-data/unit/check-dataclasses.test

+51
Original file line numberDiff line numberDiff line change
@@ -1568,3 +1568,54 @@ class B:
15681568
class Derived(A, B):
15691569
pass
15701570
[builtins fixtures/dataclasses.pyi]
1571+
1572+
[case testDataclassGenericInheritance2]
1573+
# flags: --python-version 3.7
1574+
from dataclasses import dataclass
1575+
from typing import Any, Callable, Generic, TypeVar, List
1576+
1577+
T = TypeVar("T")
1578+
S = TypeVar("S")
1579+
1580+
@dataclass
1581+
class Parent(Generic[T]):
1582+
f: Callable[[T], Any]
1583+
1584+
@dataclass
1585+
class Child(Parent[T]): ...
1586+
1587+
class A: ...
1588+
def func(obj: A) -> bool: ...
1589+
1590+
reveal_type(Child[A](func).f) # N: Revealed type is "def (__main__.A) -> Any"
1591+
1592+
@dataclass
1593+
class Parent2(Generic[T]):
1594+
a: List[T]
1595+
1596+
@dataclass
1597+
class Child2(Generic[T, S], Parent2[S]):
1598+
b: List[T]
1599+
1600+
reveal_type(Child2([A()], [1]).a) # N: Revealed type is "builtins.list[__main__.A]"
1601+
reveal_type(Child2[int, A]([A()], [1]).b) # N: Revealed type is "builtins.list[builtins.int]"
1602+
[builtins fixtures/dataclasses.pyi]
1603+
1604+
[case testDataclassInheritOptionalType]
1605+
# flags: --python-version 3.7 --strict-optional
1606+
from dataclasses import dataclass
1607+
from typing import Any, Callable, Generic, TypeVar, List, Optional
1608+
1609+
T = TypeVar("T")
1610+
1611+
@dataclass
1612+
class Parent(Generic[T]):
1613+
x: Optional[str]
1614+
@dataclass
1615+
class Child(Parent):
1616+
y: Optional[int]
1617+
Child(x=1, y=1) # E: Argument "x" to "Child" has incompatible type "int"; expected "Optional[str]"
1618+
Child(x='', y='') # E: Argument "y" to "Child" has incompatible type "str"; expected "Optional[int]"
1619+
Child(x='', y=1)
1620+
Child(x=None, y=None)
1621+
[builtins fixtures/dataclasses.pyi]

0 commit comments

Comments
 (0)