Skip to content

Commit

Permalink
Understand the self-destructing nature of Enum._ignore_ (#12128)
Browse files Browse the repository at this point in the history
  • Loading branch information
kstauffer authored Feb 16, 2022
1 parent 554606b commit eef316d
Show file tree
Hide file tree
Showing 6 changed files with 30 additions and 11 deletions.
7 changes: 4 additions & 3 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from mypy.types import (
Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike,
TypeVarLikeType, Overloaded, TypeVarType, UnionType, PartialType, TypeOfAny, LiteralType,
DeletedType, NoneType, TypeType, has_type_vars, get_proper_type, ProperType, ParamSpecType
DeletedType, NoneType, TypeType, has_type_vars, get_proper_type, ProperType, ParamSpecType,
ENUM_REMOVED_PROPS
)
from mypy.nodes import (
TypeInfo, FuncBase, Var, FuncDef, SymbolNode, SymbolTable, Context,
Expand Down Expand Up @@ -831,8 +832,8 @@ def analyze_enum_class_attribute_access(itype: Instance,
name: str,
mx: MemberContext,
) -> Optional[Type]:
# Skip "_order_" and "__order__", since Enum will remove it
if name in ("_order_", "__order__"):
# Skip these since Enum will remove it
if name in ENUM_REMOVED_PROPS:
return mx.msg.has_no_attr(
mx.original_type, itype, name, mx.context, mx.module_symbol_table
)
Expand Down
4 changes: 2 additions & 2 deletions mypy/semanal_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
)
from mypy.semanal_shared import SemanticAnalyzerInterface
from mypy.options import Options
from mypy.types import get_proper_type, LiteralType
from mypy.types import get_proper_type, LiteralType, ENUM_REMOVED_PROPS

# Note: 'enum.EnumMeta' is deliberately excluded from this list. Classes that directly use
# enum.EnumMeta do not necessarily automatically have the 'name' and 'value' attributes.
ENUM_BASES: Final = frozenset((
'enum.Enum', 'enum.IntEnum', 'enum.Flag', 'enum.IntFlag', 'enum.StrEnum',
))
ENUM_SPECIAL_PROPS: Final = frozenset((
'name', 'value', '_name_', '_value_', '_order_', '__order__',
'name', 'value', '_name_', '_value_', *ENUM_REMOVED_PROPS,
# Also attributes from `object`:
'__module__', '__annotations__', '__doc__', '__slots__', '__dict__',
))
Expand Down
7 changes: 4 additions & 3 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
TupleType, Instance, FunctionLike, Type, CallableType, TypeVarLikeType, Overloaded,
TypeVarType, UninhabitedType, FormalArgument, UnionType, NoneType,
AnyType, TypeOfAny, TypeType, ProperType, LiteralType, get_proper_type, get_proper_types,
copy_type, TypeAliasType, TypeQuery, ParamSpecType
copy_type, TypeAliasType, TypeQuery, ParamSpecType,
ENUM_REMOVED_PROPS
)
from mypy.nodes import (
FuncBase, FuncItem, FuncDef, OverloadedFuncDef, TypeInfo, ARG_STAR, ARG_STAR2, ARG_POS,
Expand Down Expand Up @@ -715,8 +716,8 @@ class Status(Enum):
for name, symbol in typ.type.names.items():
if not isinstance(symbol.node, Var):
continue
# Skip "_order_" and "__order__", since Enum will remove it
if name in ("_order_", "__order__"):
# Skip these since Enum will remove it
if name in ENUM_REMOVED_PROPS:
continue
new_items.append(LiteralType(name, typ))
# SymbolTables are really just dicts, and dicts are guaranteed to preserve
Expand Down
8 changes: 8 additions & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@
'typing_extensions.reveal_type',
)

# Attributes that can optionally be defined in the body of a subclass of
# enum.Enum but are removed from the class __dict__ by EnumMeta.
ENUM_REMOVED_PROPS: Final = (
'_ignore_',
'_order_',
'__order__',
)

NEVER_NAMES: Final = (
'typing.NoReturn',
'typing_extensions.NoReturn',
Expand Down
6 changes: 3 additions & 3 deletions mypyc/irbuild/classdef.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
ClassDef, FuncDef, OverloadedFuncDef, PassStmt, AssignmentStmt, CallExpr, NameExpr, StrExpr,
ExpressionStmt, TempNode, Decorator, Lvalue, MemberExpr, RefExpr, TypeInfo, is_class_var
)
from mypy.types import Instance, get_proper_type
from mypy.types import Instance, get_proper_type, ENUM_REMOVED_PROPS
from mypyc.ir.ops import (
Value, Register, Call, LoadErrorValue, LoadStatic, InitStatic, TupleSet, SetAttr, Return,
BasicBlock, Branch, MethodCall, NAMESPACE_TYPE, LoadAddress
Expand Down Expand Up @@ -517,8 +517,8 @@ def add_non_ext_class_attr(builder: IRBuilder,
if (
cdef.info.bases
and cdef.info.bases[0].type.fullname == 'enum.Enum'
# Skip "_order_" and "__order__", since Enum will remove it
and lvalue.name not in ('_order_', '__order__')
# Skip these since Enum will remove it
and lvalue.name not in ENUM_REMOVED_PROPS
):
# Enum values are always boxed, so use object_rprimitive.
attr_to_cache.append((lvalue, object_rprimitive))
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 @@ -1902,3 +1902,12 @@ def f2(c: C, a: Any) -> None:
y = {'y': a, 'x': c.value}
reveal_type(y) # N: Revealed type is "builtins.dict[builtins.str*, Any]"
[builtins fixtures/dict.pyi]

[case testEnumIgnoreIsDeleted]
from enum import Enum

class C(Enum):
_ignore_ = 'X'

C._ignore_ # E: "Type[C]" has no attribute "_ignore_"
[typing fixtures/typing-medium.pyi]

0 comments on commit eef316d

Please sign in to comment.