Skip to content

Commit 1970029

Browse files
authored
Special cases Enum and some special props to be non-final, refs #11699 (#11713)
Closes #11699
1 parent 5f7a3f1 commit 1970029

File tree

6 files changed

+50
-17
lines changed

6 files changed

+50
-17
lines changed

mypy/checker.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
type_object_type,
5151
analyze_decorator_or_funcbase_access,
5252
)
53+
from mypy.semanal_enum import ENUM_BASES, ENUM_SPECIAL_PROPS
5354
from mypy.typeops import (
5455
map_type_from_supertype, bind_self, erase_to_bound, make_simplified_union,
5556
erase_def_to_union_or_bound, erase_to_union_or_bound, coerce_to_literal,
@@ -2522,13 +2523,14 @@ def check_compatibility_final_super(self, node: Var,
25222523
self.msg.cant_override_final(node.name, base.name, node)
25232524
return False
25242525
if node.is_final:
2526+
if base.fullname in ENUM_BASES and node.name in ENUM_SPECIAL_PROPS:
2527+
return True
25252528
self.check_if_final_var_override_writable(node.name, base_node, node)
25262529
return True
25272530

25282531
def check_if_final_var_override_writable(self,
25292532
name: str,
2530-
base_node:
2531-
Optional[Node],
2533+
base_node: Optional[Node],
25322534
ctx: Context) -> None:
25332535
"""Check that a final variable doesn't override writeable attribute.
25342536

mypy/checkexpr.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
has_any_from_unimported_type, check_for_explicit_any, set_any_tvars, expand_type_alias,
1515
make_optional_type,
1616
)
17+
from mypy.semanal_enum import ENUM_BASES
1718
from mypy.types import (
1819
Type, AnyType, CallableType, Overloaded, NoneType, TypeVarType,
1920
TupleType, TypedDictType, Instance, ErasedType, UnionType,
@@ -1000,9 +1001,7 @@ def check_callable_call(self,
10001001
ret_type = get_proper_type(callee.ret_type)
10011002
if callee.is_type_obj() and isinstance(ret_type, Instance):
10021003
callable_name = ret_type.type.fullname
1003-
if (isinstance(callable_node, RefExpr)
1004-
and callable_node.fullname in ('enum.Enum', 'enum.IntEnum',
1005-
'enum.Flag', 'enum.IntFlag')):
1004+
if isinstance(callable_node, RefExpr) and callable_node.fullname in ENUM_BASES:
10061005
# An Enum() call that failed SemanticAnalyzerPass2.check_enum_call().
10071006
return callee.ret_type, callee
10081007

mypy/plugins/enums.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,13 @@
1818
from mypy.typeops import make_simplified_union
1919
from mypy.nodes import TypeInfo
2020
from mypy.subtypes import is_equivalent
21+
from mypy.semanal_enum import ENUM_BASES
2122

22-
# Note: 'enum.EnumMeta' is deliberately excluded from this list. Classes that directly use
23-
# enum.EnumMeta do not necessarily automatically have the 'name' and 'value' attributes.
24-
ENUM_PREFIXES: Final = {"enum.Enum", "enum.IntEnum", "enum.Flag", "enum.IntFlag"}
25-
ENUM_NAME_ACCESS: Final = {"{}.name".format(prefix) for prefix in ENUM_PREFIXES} | {
26-
"{}._name_".format(prefix) for prefix in ENUM_PREFIXES
23+
ENUM_NAME_ACCESS: Final = {"{}.name".format(prefix) for prefix in ENUM_BASES} | {
24+
"{}._name_".format(prefix) for prefix in ENUM_BASES
2725
}
28-
ENUM_VALUE_ACCESS: Final = {"{}.value".format(prefix) for prefix in ENUM_PREFIXES} | {
29-
"{}._value_".format(prefix) for prefix in ENUM_PREFIXES
26+
ENUM_VALUE_ACCESS: Final = {"{}.value".format(prefix) for prefix in ENUM_BASES} | {
27+
"{}._value_".format(prefix) for prefix in ENUM_BASES
3028
}
3129

3230

mypy/semanal.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@
116116
)
117117
from mypy.semanal_namedtuple import NamedTupleAnalyzer
118118
from mypy.semanal_typeddict import TypedDictAnalyzer
119-
from mypy.semanal_enum import EnumCallAnalyzer
119+
from mypy.semanal_enum import EnumCallAnalyzer, ENUM_BASES
120120
from mypy.semanal_newtype import NewTypeAnalyzer
121121
from mypy.reachability import (
122122
infer_reachability_of_if_statement, infer_condition_value, ALWAYS_FALSE, ALWAYS_TRUE,
@@ -1554,8 +1554,7 @@ def configure_base_classes(self,
15541554
self.fail('Cannot subclass "NewType"', defn)
15551555
if (
15561556
base.type.is_enum
1557-
and base.type.fullname not in (
1558-
'enum.Enum', 'enum.IntEnum', 'enum.Flag', 'enum.IntFlag')
1557+
and base.type.fullname not in ENUM_BASES
15591558
and base.type.names
15601559
and any(not isinstance(n.node, (FuncBase, Decorator))
15611560
for n in base.type.names.values())

mypy/semanal_enum.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55

66
from typing import List, Tuple, Optional, Union, cast
7+
from typing_extensions import Final
78

89
from mypy.nodes import (
910
Expression, Context, TypeInfo, AssignmentStmt, NameExpr, CallExpr, RefExpr, StrExpr,
@@ -13,6 +14,15 @@
1314
from mypy.semanal_shared import SemanticAnalyzerInterface
1415
from mypy.options import Options
1516

17+
# Note: 'enum.EnumMeta' is deliberately excluded from this list. Classes that directly use
18+
# enum.EnumMeta do not necessarily automatically have the 'name' and 'value' attributes.
19+
ENUM_BASES: Final = frozenset((
20+
'enum.Enum', 'enum.IntEnum', 'enum.Flag', 'enum.IntFlag',
21+
))
22+
ENUM_SPECIAL_PROPS: Final = frozenset((
23+
'name', 'value', '_name_', '_value_', '_order_', '__order__',
24+
))
25+
1626

1727
class EnumCallAnalyzer:
1828
def __init__(self, options: Options, api: SemanticAnalyzerInterface) -> None:
@@ -62,7 +72,7 @@ class A(enum.Enum):
6272
if not isinstance(callee, RefExpr):
6373
return None
6474
fullname = callee.fullname
65-
if fullname not in ('enum.Enum', 'enum.IntEnum', 'enum.Flag', 'enum.IntFlag'):
75+
if fullname not in ENUM_BASES:
6676
return None
6777
items, values, ok = self.parse_enum_call_args(call, fullname.split('.')[-1])
6878
if not ok:

test-data/unit/check-enum.test

+26-1
Original file line numberDiff line numberDiff line change
@@ -1392,7 +1392,7 @@ Medal.silver = 2 # E: Cannot assign to final attribute "silver"
13921392
from enum import Enum
13931393
class Types(Enum):
13941394
key = 0
1395-
value = 1 # E: Cannot override writable attribute "value" with a final one
1395+
value = 1
13961396

13971397

13981398
[case testEnumReusedKeys]
@@ -1677,3 +1677,28 @@ class A(Enum):
16771677
class Inner: pass
16781678
class B(A): pass # E: Cannot inherit from final class "A"
16791679
[builtins fixtures/bool.pyi]
1680+
1681+
1682+
[case testEnumFinalSpecialProps]
1683+
# https://github.com/python/mypy/issues/11699
1684+
from enum import Enum, IntEnum
1685+
1686+
class E(Enum):
1687+
name = 'a'
1688+
value = 'b'
1689+
_name_ = 'a1'
1690+
_value_ = 'b2'
1691+
_order_ = 'X Y'
1692+
__order__ = 'X Y'
1693+
1694+
class EI(IntEnum):
1695+
name = 'a'
1696+
value = 1
1697+
_name_ = 'a1'
1698+
_value_ = 2
1699+
_order_ = 'X Y'
1700+
__order__ = 'X Y'
1701+
1702+
E._order_ = 'a' # E: Cannot assign to final attribute "_order_"
1703+
EI.value = 2 # E: Cannot assign to final attribute "value"
1704+
[builtins fixtures/bool.pyi]

0 commit comments

Comments
 (0)