From ce01710f2a9e198a52e685b947e39c6ff78985c8 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 22 Apr 2022 13:05:47 +0100 Subject: [PATCH 1/4] Fix types of inherited attributes in generic dataclasses Fixes #12633. --- mypy/plugins/dataclasses.py | 6 ++--- test-data/unit/check-dataclasses.test | 32 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 18caed8bbb8e..5e2572e30dda 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -101,10 +101,8 @@ def deserialize( def expand_typevar_from_subtype(self, sub_type: TypeInfo) -> None: """Expands type vars in the context of a subtype when an attribute is inherited from a generic super type.""" - if not isinstance(self.type, TypeVarType): - return - - self.type = map_type_from_supertype(self.type, sub_type, self.info) + if self.type is not None: + self.type = map_type_from_supertype(self.type, sub_type, self.info) class DataclassTransformer: diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 594df103841c..1139ad90f64c 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1568,3 +1568,35 @@ class B: class Derived(A, B): pass [builtins fixtures/dataclasses.pyi] + +[case testDataclassGenericInheritance2] +# flags: --python-version 3.7 +from dataclasses import dataclass +from typing import Any, Callable, Generic, TypeVar, List + +T = TypeVar("T") +S = TypeVar("S") + +@dataclass +class Parent(Generic[T]): + f: Callable[[T], Any] + +@dataclass +class Child(Parent[T]): ... + +class A: ... +def func(obj: A) -> bool: ... + +reveal_type(Child[A](func).f) # N: Revealed type is "def (__main__.A) -> Any" + +@dataclass +class Parent2(Generic[T]): + a: List[T] + +@dataclass +class Child2(Generic[T, S], Parent2[S]): + b: List[T] + +reveal_type(Child2([A()], [1]).a) # N: Revealed type is "builtins.list[__main__.A]" +reveal_type(Child2[int, A]([A()], [1]).b) # N: Revealed type is "builtins.list[builtins.int]" +[builtins fixtures/dataclasses.pyi] From 9dc5a761b5d49d5f179bd4606c26bb4323e9e770 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 22 Apr 2022 15:54:27 +0100 Subject: [PATCH 2/4] Add another test case --- test-data/unit/check-dataclasses.test | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 1139ad90f64c..7ddfdb6e5df9 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1600,3 +1600,23 @@ class Child2(Generic[T, S], Parent2[S]): reveal_type(Child2([A()], [1]).a) # N: Revealed type is "builtins.list[__main__.A]" reveal_type(Child2[int, A]([A()], [1]).b) # N: Revealed type is "builtins.list[builtins.int]" [builtins fixtures/dataclasses.pyi] + +[case testDataclassInheritOptionalType] +# flags: --python-version 3.7 --strict-optional +from dataclasses import dataclass +from typing import Any, Callable, Generic, TypeVar, List, Optional + +T = TypeVar("T") + +@dataclass +class Parent(Generic[T]): + x: Optional[str] +@dataclass +class Child(Parent): + y: Optional[int] +Child(x=1, y=1) # E: Argument "x" to "Child" has incompatible type "int"; expected "str" +Child(x='', y='') # E: Argument "y" to "Child" has incompatible type "str"; expected "Optional[int]" +Child(x='', y=1) +Child(x=None, y=None) +x: int = None +[builtins fixtures/dataclasses.pyi] From 653bbd89055b93079190286e71f8c1d793846ae9 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 25 Apr 2022 11:02:34 +0100 Subject: [PATCH 3/4] Update test case --- test-data/unit/check-dataclasses.test | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 7ddfdb6e5df9..bce1ee24a31a 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1614,9 +1614,8 @@ class Parent(Generic[T]): @dataclass class Child(Parent): y: Optional[int] -Child(x=1, y=1) # E: Argument "x" to "Child" has incompatible type "int"; expected "str" +Child(x=1, y=1) # E: Argument "x" to "Child" has incompatible type "int"; expected "Optional[str]" Child(x='', y='') # E: Argument "y" to "Child" has incompatible type "str"; expected "Optional[int]" Child(x='', y=1) Child(x=None, y=None) -x: int = None [builtins fixtures/dataclasses.pyi] From 4541c2b9d9c8b3497a48340f127acddee2cc3950 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 25 Apr 2022 11:24:35 +0100 Subject: [PATCH 4/4] Explicitly set strict optional state --- mypy/plugins/dataclasses.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 5e2572e30dda..5827a21e8ccf 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -19,6 +19,7 @@ get_proper_type, AnyType, TypeOfAny, ) from mypy.server.trigger import make_wildcard_trigger +from mypy.state import state # The set of decorators that generate dataclasses. dataclass_makers: Final = { @@ -400,7 +401,11 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]: name: str = data["name"] if name not in known_attrs: attr = DataclassAttribute.deserialize(info, data, ctx.api) - attr.expand_typevar_from_subtype(ctx.cls.info) + # TODO: We shouldn't be performing type operations during the main + # semantic analysis pass, since some TypeInfo attributes might + # still be in flux. This should be performed in a later phase. + with state.strict_optional_set(ctx.api.options.strict_optional): + attr.expand_typevar_from_subtype(ctx.cls.info) known_attrs.add(name) super_attrs.append(attr) elif all_attrs: