Skip to content

Commit

Permalink
Produce error when assigning Enum or TypedDict as attribute (#8107)
Browse files Browse the repository at this point in the history
  • Loading branch information
TH3CHARLie authored and msullivan committed Dec 12, 2019
1 parent bc3e9d4 commit a2ab97b
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 3 deletions.
5 changes: 4 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2161,14 +2161,17 @@ 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
is_typed_dict, info = self.typed_dict_analyzer.check_typeddict(s.rvalue, name,
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)
Expand Down
7 changes: 5 additions & 2 deletions mypy/semanal_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
9 changes: 9 additions & 0 deletions test-data/unit/check-enum.test
Original file line number Diff line number Diff line change
Expand Up @@ -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'
11 changes: 11 additions & 0 deletions test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]

0 comments on commit a2ab97b

Please sign in to comment.