From 7692f56f76787bee67b2047de65b46750d1cac9f Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 13 Jun 2019 14:43:53 +0100 Subject: [PATCH] New semantic analyzer: fix crash related to dataclasses.InitVar (#6984) Previously InitVar attributes were missing on the second semantic analysis pass, since the dataclasses plugin removes those attributes from the class symbol table. This caused a crash. The fix is to reset the attribute declarations so that they will be re-added to the symbol table on successive semantic analysis passes. Fixes #6955. --- mypy/plugins/dataclasses.py | 20 ++++++++++++++++---- test-data/unit/check-dataclasses.test | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 8d3b02368b22..31e725a8cfc4 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -1,3 +1,5 @@ +"""Plugin that provides support for dataclasses.""" + from collections import OrderedDict from typing import Dict, List, Set, Tuple @@ -166,16 +168,26 @@ def transform(self) -> None: if decorator_arguments['frozen']: self._freeze(attributes) - # Remove init-only vars from the class. - for attr in attributes: - if attr.is_init_var: - del info.names[attr.name] + self.reset_init_only_vars(info, attributes) info.metadata['dataclass'] = { 'attributes': OrderedDict((attr.name, attr.serialize()) for attr in attributes), 'frozen': decorator_arguments['frozen'], } + def reset_init_only_vars(self, info: TypeInfo, attributes: List[DataclassAttribute]) -> None: + """Remove init-only vars from the class and reset init var declarations.""" + for attr in attributes: + if attr.is_init_var: + del info.names[attr.name] + for stmt in info.defn.defs.body: + if isinstance(stmt, AssignmentStmt) and stmt.unanalyzed_type: + lvalue = stmt.lvalues[0] + if isinstance(lvalue, NameExpr) and lvalue.name == attr.name: + # Reset node so that another semantic analysis pass will + # recreate a symbol node for this attribute. + lvalue.node = None + def collect_attributes(self) -> List[DataclassAttribute]: """Collect all attributes declared in the dataclass and its parents. diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 53202ec5e65f..47ec45e5e8bb 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -520,6 +520,26 @@ app.database_name # E: "SpecializedApplication" has no attribute "database_name [builtins fixtures/list.pyi] +[case testDataclassesInitVarsAndDefer] +# flags: --new-semantic-analyzer +from dataclasses import InitVar, dataclass + +defer: Yes + +@dataclass +class Application: + name: str + database_name: InitVar[str] + +reveal_type(Application) # E: Revealed type is 'def (name: builtins.str, database_name: builtins.str) -> __main__.Application' +app = Application("example", 42) # E: Argument 2 to "Application" has incompatible type "int"; expected "str" +app = Application("example", "apps") +app.name +app.database_name # E: "Application" has no attribute "database_name" + +class Yes: ... +[builtins fixtures/list.pyi] + [case testDataclassFactory] from typing import Type, TypeVar from dataclasses import dataclass