Skip to content

Understand the self-destructing nature of Enum._ignore_ #12128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]