From 1cd69b56b77eadb6d8c990c122f049f9bf172d05 Mon Sep 17 00:00:00 2001 From: TH3CHARLie Date: Sun, 8 Dec 2019 22:03:03 +0800 Subject: [PATCH] produce error when assigning Enum or TypedDict as attribute --- mypy/semanal.py | 5 ++++- mypy/semanal_enum.py | 7 +++++-- test-data/unit/check-enum.test | 9 +++++++++ test-data/unit/check-typeddict.test | 11 +++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index f1fb8ff8be932..ffd44541af856 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2161,7 +2161,7 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool: """Check if s defines a typed dict.""" if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.analyzed, TypedDictExpr): return True # This is a valid and analyzed typed dict definition, nothing to do here. - if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr): + if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)): return False lvalue = s.lvalues[0] name = lvalue.name @@ -2169,6 +2169,9 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool: self.is_func_scope()) if not is_typed_dict: return False + if isinstance(lvalue, MemberExpr): + self.fail("TypedDict type as attribute is not supported", lvalue) + return False # Yes, it's a valid typed dict, but defer if it is not ready. if not info: self.mark_incomplete(name, lvalue, becomes_typeinfo=True) diff --git a/mypy/semanal_enum.py b/mypy/semanal_enum.py index d373a887abfa7..8e62b0c247a99 100644 --- a/mypy/semanal_enum.py +++ b/mypy/semanal_enum.py @@ -8,7 +8,7 @@ from mypy.nodes import ( Expression, Context, TypeInfo, AssignmentStmt, NameExpr, CallExpr, RefExpr, StrExpr, UnicodeExpr, TupleExpr, ListExpr, DictExpr, Var, SymbolTableNode, MDEF, ARG_POS, - EnumCallExpr + EnumCallExpr, MemberExpr ) from mypy.semanal_shared import SemanticAnalyzerInterface from mypy.options import Options @@ -25,13 +25,16 @@ def process_enum_call(self, s: AssignmentStmt, is_func_scope: bool) -> bool: Return True if this looks like an Enum definition (but maybe with errors), otherwise return False. """ - if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr): + if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)): return False lvalue = s.lvalues[0] name = lvalue.name enum_call = self.check_enum_call(s.rvalue, name, is_func_scope) if enum_call is None: return False + if isinstance(lvalue, MemberExpr): + self.fail("Enum type as attribute is not supported", lvalue) + return False # Yes, it's a valid Enum definition. Add it to the symbol table. self.api.add_symbol(name, enum_call, s) return True diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 6be7a046e8234..5e5eae417d26b 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -908,3 +908,12 @@ def func(x: Union[int, None, Empty] = _empty) -> int: reveal_type(x) # N: Revealed type is 'builtins.int' return x + 2 [builtins fixtures/primitives.pyi] + +[case testAssignEnumAsAttribute] +from enum import Enum + +class A: + def __init__(self) -> None: + self.b = Enum("x", [("foo", "bar")]) # E: Enum type as attribute is not supported + +reveal_type(A().b) # N: Revealed type is 'Any' diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 70d99fc602404..96eeb24c93574 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -1987,3 +1987,14 @@ reveal_type(foo['bar']) # N: Revealed type is 'builtins.list[Any]' reveal_type(foo['baz']) # N: Revealed type is 'builtins.list[Any]' [builtins fixtures/dict.pyi] [typing fixtures/typing-full.pyi] + +[case testAssignTypedDictAsAttribute] +from typing import TypedDict + +class A: + def __init__(self) -> None: + self.b = TypedDict('b', {'x': int, 'y': str}) # E: TypedDict type as attribute is not supported + +reveal_type(A().b) # N: Revealed type is 'Any' +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi]