From 7908badd5c046a13315b70e6d807992592927e88 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sun, 15 May 2022 10:48:40 +0100 Subject: [PATCH 1/3] Fix crash on type alias definition inside dataclass declaration --- mypy/plugins/dataclasses.py | 38 ++++++++++++++++++++++++--- test-data/unit/check-dataclasses.test | 30 +++++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 24077bb4a549..48444f2e05f2 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -1,11 +1,11 @@ """Plugin that provides support for dataclasses.""" -from typing import Dict, List, Set, Tuple, Optional +from typing import Dict, List, Set, Tuple, Optional, Union from typing_extensions import Final from mypy.nodes import ( ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_POS, ARG_STAR, ARG_STAR2, MDEF, - Argument, AssignmentStmt, CallExpr, Context, Expression, JsonDict, + Argument, AssignmentStmt, CallExpr, TypeAlias, Context, Expression, JsonDict, NameExpr, RefExpr, SymbolTableNode, TempNode, TypeInfo, Var, TypeVarExpr, PlaceholderNode ) @@ -16,7 +16,7 @@ from mypy.typeops import map_type_from_supertype from mypy.types import ( Type, Instance, NoneType, TypeVarType, CallableType, TupleType, LiteralType, - get_proper_type, AnyType, TypeOfAny, + get_proper_type, AnyType, TypeOfAny, TypeType, ) from mypy.server.trigger import make_wildcard_trigger from mypy.state import state @@ -333,6 +333,38 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]: node = sym.node assert not isinstance(node, PlaceholderNode) + + if isinstance(node, TypeAlias): + ctx.api.fail( + ( + 'Type aliases inside dataclass definitions ' + 'are not supported at runtime.' + ), + Context(line=node.line, column=node.column) + ) + # Now do our best to simulate the runtime, + # which treates a TypeAlias definition in a dataclass class + # as an instance field with a default value. + # + # Replace the `TypeAlias` node with a `Var` node, so that we do the same. + target, fullname = node.target, node.fullname + proper_target = get_proper_type(target) + var_type: Union[TypeType, AnyType] + if isinstance(proper_target, Instance): + var_type = TypeType(proper_target, line=node.line, column=node.column) + # Something else -- fallback to Any + else: + var_type = AnyType(TypeOfAny.from_error) + var = Var(name=fullname, type=var_type) + var.info = cls.info + var._fullname = fullname + cls.info.names[fullname] = SymbolTableNode( + kind=MDEF, + node=var, + plugin_generated=True, + ) + sym.node = node = var + assert isinstance(node, Var) # x: ClassVar[int] is ignored by dataclasses. diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 4cddc59b0153..79d010d7928b 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -513,6 +513,36 @@ Application.COUNTER = 1 [builtins fixtures/dataclasses.pyi] +[case testTypeAliasInDataclassDoesNotCrash] +# flags: --python-version 3.7 +from dataclasses import dataclass +from typing import Callable +from typing_extensions import TypeAlias + +@dataclass +class Foo: + x: int + +@dataclass +class One: + S: TypeAlias = Foo # E: Type aliases inside dataclass definitions are not supported at runtime. + +a = One() +b = One(Foo) +reveal_type(a.S) # N: Revealed type is "Type[__main__.Foo]" +reveal_type(b.S) # N: Revealed type is "Type[__main__.Foo]" +a.S() # E: Missing positional argument "x" in call to "Foo" +reveal_type(a.S(5)) # N: Revealed type is "__main__.Foo" +reveal_type(b.S(98)) # N: Revealed type is "__main__.Foo" + +@dataclass +class Two: + S: TypeAlias = Callable[[int], str] # E: Type aliases inside dataclass definitions are not supported at runtime. + +c = Two() +reveal_type(c.S) # N: Revealed type is "Any" +[builtins fixtures/dataclasses.pyi] + [case testDataclassOrdering] # flags: --python-version 3.7 from dataclasses import dataclass From 766bd38dacf6693500324063de6cf4706863b55b Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Wed, 18 May 2022 16:14:42 +0100 Subject: [PATCH 2/3] Mostly address review --- mypy/plugins/dataclasses.py | 11 +++-------- test-data/unit/check-dataclasses.test | 4 ++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 48444f2e05f2..dc5289d6270b 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -338,9 +338,9 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]: ctx.api.fail( ( 'Type aliases inside dataclass definitions ' - 'are not supported at runtime.' + 'are not supported at runtime' ), - Context(line=node.line, column=node.column) + node ) # Now do our best to simulate the runtime, # which treates a TypeAlias definition in a dataclass class @@ -355,14 +355,9 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]: # Something else -- fallback to Any else: var_type = AnyType(TypeOfAny.from_error) - var = Var(name=fullname, type=var_type) + var = Var(name=node.name, type=var_type) var.info = cls.info var._fullname = fullname - cls.info.names[fullname] = SymbolTableNode( - kind=MDEF, - node=var, - plugin_generated=True, - ) sym.node = node = var assert isinstance(node, Var) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 79d010d7928b..3ee1318785da 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -525,7 +525,7 @@ class Foo: @dataclass class One: - S: TypeAlias = Foo # E: Type aliases inside dataclass definitions are not supported at runtime. + S: TypeAlias = Foo # E: Type aliases inside dataclass definitions are not supported at runtime a = One() b = One(Foo) @@ -537,7 +537,7 @@ reveal_type(b.S(98)) # N: Revealed type is "__main__.Foo" @dataclass class Two: - S: TypeAlias = Callable[[int], str] # E: Type aliases inside dataclass definitions are not supported at runtime. + S: TypeAlias = Callable[[int], str] # E: Type aliases inside dataclass definitions are not supported at runtime c = Two() reveal_type(c.S) # N: Revealed type is "Any" From ffb99357a506f35eb0287614db58cdba38659dd1 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Wed, 18 May 2022 16:26:58 +0100 Subject: [PATCH 3/3] Skip processing the node, avoid modifying the SymbolTable --- mypy/plugins/dataclasses.py | 25 ++++++------------------- test-data/unit/check-dataclasses.test | 8 +++----- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index dc5289d6270b..00c46e1417c5 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -1,6 +1,6 @@ """Plugin that provides support for dataclasses.""" -from typing import Dict, List, Set, Tuple, Optional, Union +from typing import Dict, List, Set, Tuple, Optional from typing_extensions import Final from mypy.nodes import ( @@ -16,7 +16,7 @@ from mypy.typeops import map_type_from_supertype from mypy.types import ( Type, Instance, NoneType, TypeVarType, CallableType, TupleType, LiteralType, - get_proper_type, AnyType, TypeOfAny, TypeType, + get_proper_type, AnyType, TypeOfAny, ) from mypy.server.trigger import make_wildcard_trigger from mypy.state import state @@ -342,23 +342,10 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]: ), node ) - # Now do our best to simulate the runtime, - # which treates a TypeAlias definition in a dataclass class - # as an instance field with a default value. - # - # Replace the `TypeAlias` node with a `Var` node, so that we do the same. - target, fullname = node.target, node.fullname - proper_target = get_proper_type(target) - var_type: Union[TypeType, AnyType] - if isinstance(proper_target, Instance): - var_type = TypeType(proper_target, line=node.line, column=node.column) - # Something else -- fallback to Any - else: - var_type = AnyType(TypeOfAny.from_error) - var = Var(name=node.name, type=var_type) - var.info = cls.info - var._fullname = fullname - sym.node = node = var + # Skip processing this node. This doesn't match the runtime behaviour, + # but the only alternative would be to modify the SymbolTable, + # and it's a little hairy to do that in a plugin. + continue assert isinstance(node, Var) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 3ee1318785da..ca6b53490106 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -528,19 +528,17 @@ class One: S: TypeAlias = Foo # E: Type aliases inside dataclass definitions are not supported at runtime a = One() -b = One(Foo) -reveal_type(a.S) # N: Revealed type is "Type[__main__.Foo]" -reveal_type(b.S) # N: Revealed type is "Type[__main__.Foo]" +reveal_type(a.S) # N: Revealed type is "def (x: builtins.int) -> __main__.Foo" a.S() # E: Missing positional argument "x" in call to "Foo" reveal_type(a.S(5)) # N: Revealed type is "__main__.Foo" -reveal_type(b.S(98)) # N: Revealed type is "__main__.Foo" @dataclass class Two: S: TypeAlias = Callable[[int], str] # E: Type aliases inside dataclass definitions are not supported at runtime c = Two() -reveal_type(c.S) # N: Revealed type is "Any" +x = c.S # E: Member "S" is not assignable +reveal_type(x) # N: Revealed type is "Any" [builtins fixtures/dataclasses.pyi] [case testDataclassOrdering]