From 1125cb8a5015c58434a8d9518cf226552874d443 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 2 Jul 2019 15:20:40 +0100 Subject: [PATCH 1/5] Expect placeholders in attrs plugin --- mypy/plugins/attrs.py | 4 +++- test-data/unit/check-attr.test | 35 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 78afc422add6..f1866973e14c 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -270,7 +270,9 @@ def _analyze_class(ctx: 'mypy.plugin.ClassDefContext', # instance level assignments. if attribute.name in ctx.cls.info.names: node = ctx.cls.info.names[attribute.name].node - assert isinstance(node, Var) + if not isinstance(node, Var): + # This node may be not ready yet (i.e. a PlaceholderNode). + continue node.is_initialized_in_class = False # Traverse the MRO and collect attributes from the parents. diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index 6202b4e6aed4..f44b05ab3b8c 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-attr.test @@ -1107,3 +1107,38 @@ C(43) class Yes: ... [builtins fixtures/exception.pyi] + +[case testTypeInAttrBad] +import attr + +@attr.s +class C: + total = attr.ib(type=Bad) # E: Name 'Bad' is not defined + +[builtins fixtures/bool.pyi] + +[case testTypeInAttrBad2] +import attr + +@attr.s +class C: + total = attr.ib(type=Forward) + +reveal_type(C.total) # N: Revealed type is '__main__.Forward' +C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "Forward" +class Forward: ... +[builtins fixtures/bool.pyi] + +[case testTypeInAttrBad3] +import attr + +@attr.s +class C: + total = attr.ib(default=func()) + +def func() -> int: ... + +C() +C(1) +C(1, 2) # E: Too many arguments for "C" +[builtins fixtures/bool.pyi] From a7cbc48b0b921fc97ff67dafe5fbdac64410d65c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 2 Jul 2019 15:50:42 +0100 Subject: [PATCH 2/5] One more test --- test-data/unit/check-attr.test | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index f44b05ab3b8c..1f00947f4df8 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-attr.test @@ -1114,7 +1114,6 @@ import attr @attr.s class C: total = attr.ib(type=Bad) # E: Name 'Bad' is not defined - [builtins fixtures/bool.pyi] [case testTypeInAttrBad2] @@ -1142,3 +1141,13 @@ C() C(1) C(1, 2) # E: Too many arguments for "C" [builtins fixtures/bool.pyi] + +[case testTypeInAttrBad4] +import attr + +@attr.s(frozen=True) +class C: + total = attr.ib(type=Bad) # E: Name 'Bad' is not defined + +C(0).total = 1 # E: Property "total" defined in "C" is read-only. +[builtins fixtures/bool.pyi] From c5fceaa0a91e3acbeae9a66c8400add5e7f1f394 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 2 Jul 2019 15:59:41 +0100 Subject: [PATCH 3/5] The same for dataclasses --- mypy/plugins/dataclasses.py | 4 +++- test-data/unit/check-dataclasses.test | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index dbe3695bf260..960111a4a22e 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -214,7 +214,9 @@ def collect_attributes(self) -> List[DataclassAttribute]: continue node = cls.info.names[lhs.name].node - assert isinstance(node, Var) + if not isinstance(node, Var): + # This node may be not ready yet (i.e. a PlaceholderNode). + continue # x: ClassVar[int] is ignored by dataclasses. if node.is_classvar: diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index cac18b239d62..19b30d79bbc5 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -642,3 +642,25 @@ a < b class Yes: ... [builtins fixtures/list.pyi] + +[case testDataclassFieldDeferred] +from dataclasses import field, dataclass + +@dataclass +class C: + x: int = field(default=func()) + +def func() -> int: ... +C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "int" + +[case testDataclassFieldDeferredFrozen] +from dataclasses import field, dataclass + +@dataclass(frozen=True) +class C: + x: int = field(default=func()) + +def func() -> int: ... +c: C +c.x = 1 # E: Property "x" defined in "C" is read-only +[builtins fixtures/bool.pyi] From 717a1e53eaf99b03d46283f25c6fcacdf149b0e3 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 2 Jul 2019 17:34:13 +0100 Subject: [PATCH 4/5] Fix fixture --- test-data/unit/check-attr.test | 2 +- test-data/unit/check-dataclasses.test | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index 1f00947f4df8..d90447875e25 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-attr.test @@ -1149,5 +1149,5 @@ import attr class C: total = attr.ib(type=Bad) # E: Name 'Bad' is not defined -C(0).total = 1 # E: Property "total" defined in "C" is read-only. +C(0).total = 1 # E: Property "total" defined in "C" is read-only [builtins fixtures/bool.pyi] diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 19b30d79bbc5..5150c257b2fd 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -652,6 +652,7 @@ class C: def func() -> int: ... C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "int" +[builtins fixtures/bool.pyi] [case testDataclassFieldDeferredFrozen] from dataclasses import field, dataclass From aa488f4a3af47b552119014c809839d9980a7228 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 2 Jul 2019 17:55:44 +0100 Subject: [PATCH 5/5] Address CR --- mypy/plugins/attrs.py | 7 ++++--- mypy/plugins/dataclasses.py | 7 ++++--- test-data/unit/check-attr.test | 8 ++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index f1866973e14c..d97e92875977 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -13,7 +13,7 @@ TupleExpr, ListExpr, NameExpr, CallExpr, RefExpr, FuncDef, is_class_var, TempNode, Decorator, MemberExpr, Expression, SymbolTableNode, MDEF, JsonDict, OverloadedFuncDef, ARG_NAMED_OPT, ARG_NAMED, - TypeVarExpr + TypeVarExpr, PlaceholderNode ) from mypy.plugins.common import ( _get_argument, _get_bool_argument, _get_decorator_bool_argument, add_method @@ -270,9 +270,10 @@ def _analyze_class(ctx: 'mypy.plugin.ClassDefContext', # instance level assignments. if attribute.name in ctx.cls.info.names: node = ctx.cls.info.names[attribute.name].node - if not isinstance(node, Var): - # This node may be not ready yet (i.e. a PlaceholderNode). + if isinstance(node, PlaceholderNode): + # This node is not ready yet. continue + assert isinstance(node, Var) node.is_initialized_in_class = False # Traverse the MRO and collect attributes from the parents. diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 960111a4a22e..5e9a895d803a 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -8,7 +8,7 @@ from mypy.nodes import ( ARG_OPT, ARG_POS, MDEF, Argument, AssignmentStmt, CallExpr, Context, Expression, FuncDef, JsonDict, NameExpr, RefExpr, - SymbolTableNode, TempNode, TypeInfo, Var, TypeVarExpr + SymbolTableNode, TempNode, TypeInfo, Var, TypeVarExpr, PlaceholderNode ) from mypy.plugin import ClassDefContext from mypy.plugins.common import add_method, _get_decorator_bool_argument @@ -214,9 +214,10 @@ def collect_attributes(self) -> List[DataclassAttribute]: continue node = cls.info.names[lhs.name].node - if not isinstance(node, Var): - # This node may be not ready yet (i.e. a PlaceholderNode). + if isinstance(node, PlaceholderNode): + # This node is not ready yet. continue + assert isinstance(node, Var) # x: ClassVar[int] is ignored by dataclasses. if node.is_classvar: diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index d90447875e25..070528b7495a 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-attr.test @@ -1108,7 +1108,7 @@ C(43) class Yes: ... [builtins fixtures/exception.pyi] -[case testTypeInAttrBad] +[case testTypeInAttrUndefined] import attr @attr.s @@ -1116,7 +1116,7 @@ class C: total = attr.ib(type=Bad) # E: Name 'Bad' is not defined [builtins fixtures/bool.pyi] -[case testTypeInAttrBad2] +[case testTypeInAttrForwardInRuntime] import attr @attr.s @@ -1128,7 +1128,7 @@ C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "Forward" class Forward: ... [builtins fixtures/bool.pyi] -[case testTypeInAttrBad3] +[case testDefaultInAttrForward] import attr @attr.s @@ -1142,7 +1142,7 @@ C(1) C(1, 2) # E: Too many arguments for "C" [builtins fixtures/bool.pyi] -[case testTypeInAttrBad4] +[case testTypeInAttrUndefinedFrozen] import attr @attr.s(frozen=True)