Skip to content

Commit fdcda96

Browse files
Makes Enum members implicitly final, refs #5599 (#10852)
refs #5599 This change allows to catch this error by making all Enum members implicitly Final. Also modifies Enum plugin, since it was not ready to work with `Literal[True]` and `Literal[False]`. Co-authored-by: Shantanu <[email protected]>
1 parent dde8fd8 commit fdcda96

File tree

3 files changed

+93
-13
lines changed

3 files changed

+93
-13
lines changed

mypy/plugins/enums.py

+29-2
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
we actually bake some of it directly in to the semantic analysis layer (see
1111
semanal_enum.py).
1212
"""
13-
from typing import Iterable, Optional, TypeVar
13+
from typing import Iterable, Optional, Sequence, TypeVar, cast
1414
from typing_extensions import Final
1515

1616
import mypy.plugin # To avoid circular imports.
1717
from mypy.types import Type, Instance, LiteralType, CallableType, ProperType, get_proper_type
18+
from mypy.typeops import make_simplified_union
1819
from mypy.nodes import TypeInfo
20+
from mypy.subtypes import is_equivalent
1921

2022
# Note: 'enum.EnumMeta' is deliberately excluded from this list. Classes that directly use
2123
# enum.EnumMeta do not necessarily automatically have the 'name' and 'value' attributes.
@@ -165,19 +167,44 @@ class SomeEnum:
165167
get_proper_type(n.type) if n else None
166168
for n in stnodes
167169
if n is None or not n.implicit)
168-
proper_types = (
170+
proper_types = list(
169171
_infer_value_type_with_auto_fallback(ctx, t)
170172
for t in node_types
171173
if t is None or not isinstance(t, CallableType))
172174
underlying_type = _first(proper_types)
173175
if underlying_type is None:
174176
return ctx.default_attr_type
177+
178+
# At first we try to predict future `value` type if all other items
179+
# have the same type. For example, `int`.
180+
# If this is the case, we simply return this type.
181+
# See https://github.com/python/mypy/pull/9443
175182
all_same_value_type = all(
176183
proper_type is not None and proper_type == underlying_type
177184
for proper_type in proper_types)
178185
if all_same_value_type:
179186
if underlying_type is not None:
180187
return underlying_type
188+
189+
# But, after we started treating all `Enum` values as `Final`,
190+
# we start to infer types in
191+
# `item = 1` as `Literal[1]`, not just `int`.
192+
# So, for example types in this `Enum` will all be different:
193+
#
194+
# class Ordering(IntEnum):
195+
# one = 1
196+
# two = 2
197+
# three = 3
198+
#
199+
# We will infer three `Literal` types here.
200+
# They are not the same, but they are equivalent.
201+
# So, we unify them to make sure `.value` prediction still works.
202+
# Result will be `Literal[1] | Literal[2] | Literal[3]` for this case.
203+
all_equivalent_types = all(
204+
proper_type is not None and is_equivalent(proper_type, underlying_type)
205+
for proper_type in proper_types)
206+
if all_equivalent_types:
207+
return make_simplified_union(cast(Sequence[Type], proper_types))
181208
return ctx.default_attr_type
182209

183210
assert isinstance(ctx.type, Instance)

mypy/semanal.py

+23-3
Original file line numberDiff line numberDiff line change
@@ -2411,10 +2411,30 @@ def store_final_status(self, s: AssignmentStmt) -> None:
24112411
(isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs)):
24122412
node.final_unset_in_class = True
24132413
else:
2414-
# Special case: deferred initialization of a final attribute in __init__.
2415-
# In this case we just pretend this is a valid final definition to suppress
2416-
# errors about assigning to final attribute.
24172414
for lval in self.flatten_lvalues(s.lvalues):
2415+
# Special case: we are working with an `Enum`:
2416+
#
2417+
# class MyEnum(Enum):
2418+
# key = 'some value'
2419+
#
2420+
# Here `key` is implicitly final. In runtime, code like
2421+
#
2422+
# MyEnum.key = 'modified'
2423+
#
2424+
# will fail with `AttributeError: Cannot reassign members.`
2425+
# That's why we need to replicate this.
2426+
if (isinstance(lval, NameExpr) and
2427+
isinstance(self.type, TypeInfo) and
2428+
self.type.is_enum):
2429+
cur_node = self.type.names.get(lval.name, None)
2430+
if (cur_node and isinstance(cur_node.node, Var) and
2431+
not (isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs)):
2432+
cur_node.node.is_final = True
2433+
s.is_final_def = True
2434+
2435+
# Special case: deferred initialization of a final attribute in __init__.
2436+
# In this case we just pretend this is a valid final definition to suppress
2437+
# errors about assigning to final attribute.
24182438
if isinstance(lval, MemberExpr) and self.is_self_member_ref(lval):
24192439
assert self.type, "Self member outside a class"
24202440
cur_node = self.type.names.get(lval.name, None)

test-data/unit/check-enum.test

+41-8
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class Truth(Enum):
5656
x = ''
5757
x = Truth.true.name
5858
reveal_type(Truth.true.name) # N: Revealed type is "Literal['true']?"
59-
reveal_type(Truth.false.value) # N: Revealed type is "builtins.bool"
59+
reveal_type(Truth.false.value) # N: Revealed type is "Literal[False]?"
6060
[builtins fixtures/bool.pyi]
6161

6262
[case testEnumValueExtended]
@@ -66,7 +66,7 @@ class Truth(Enum):
6666
false = False
6767

6868
def infer_truth(truth: Truth) -> None:
69-
reveal_type(truth.value) # N: Revealed type is "builtins.bool"
69+
reveal_type(truth.value) # N: Revealed type is "Union[Literal[True]?, Literal[False]?]"
7070
[builtins fixtures/bool.pyi]
7171

7272
[case testEnumValueAllAuto]
@@ -90,7 +90,7 @@ def infer_truth(truth: Truth) -> None:
9090
[builtins fixtures/primitives.pyi]
9191

9292
[case testEnumValueExtraMethods]
93-
from enum import Enum, auto
93+
from enum import Enum
9494
class Truth(Enum):
9595
true = True
9696
false = False
@@ -99,7 +99,7 @@ class Truth(Enum):
9999
return 'bar'
100100

101101
def infer_truth(truth: Truth) -> None:
102-
reveal_type(truth.value) # N: Revealed type is "builtins.bool"
102+
reveal_type(truth.value) # N: Revealed type is "Union[Literal[True]?, Literal[False]?]"
103103
[builtins fixtures/bool.pyi]
104104

105105
[case testEnumValueCustomAuto]
@@ -129,6 +129,20 @@ def cannot_infer_truth(truth: Truth) -> None:
129129
reveal_type(truth.value) # N: Revealed type is "Any"
130130
[builtins fixtures/bool.pyi]
131131

132+
[case testEnumValueSameType]
133+
from enum import Enum
134+
135+
def newbool() -> bool:
136+
...
137+
138+
class Truth(Enum):
139+
true = newbool()
140+
false = newbool()
141+
142+
def infer_truth(truth: Truth) -> None:
143+
reveal_type(truth.value) # N: Revealed type is "builtins.bool"
144+
[builtins fixtures/bool.pyi]
145+
132146
[case testEnumUnique]
133147
import enum
134148
@enum.unique
@@ -1362,6 +1376,25 @@ class E(IntEnum):
13621376
reveal_type(E.A.value) # N: Revealed type is "__main__.N"
13631377

13641378

1379+
[case testEnumFinalValues]
1380+
from enum import Enum
1381+
class Medal(Enum):
1382+
gold = 1
1383+
silver = 2
1384+
1385+
# Another value:
1386+
Medal.gold = 0 # E: Cannot assign to final attribute "gold"
1387+
# Same value:
1388+
Medal.silver = 2 # E: Cannot assign to final attribute "silver"
1389+
1390+
1391+
[case testEnumFinalValuesCannotRedefineValueProp]
1392+
from enum import Enum
1393+
class Types(Enum):
1394+
key = 0
1395+
value = 1 # E: Cannot override writable attribute "value" with a final one
1396+
1397+
13651398
[case testEnumReusedKeys]
13661399
# https://github.com/python/mypy/issues/11248
13671400
from enum import Enum
@@ -1405,13 +1438,13 @@ class NonEmptyIntFlag(IntFlag):
14051438
x = 1
14061439

14071440
class ErrorEnumWithValue(NonEmptyEnum): # E: Cannot inherit from final class "NonEmptyEnum"
1408-
x = 1
1441+
x = 1 # E: Cannot override final attribute "x" (previously declared in base class "NonEmptyEnum")
14091442
class ErrorIntEnumWithValue(NonEmptyIntEnum): # E: Cannot inherit from final class "NonEmptyIntEnum"
1410-
x = 1
1443+
x = 1 # E: Cannot override final attribute "x" (previously declared in base class "NonEmptyIntEnum")
14111444
class ErrorFlagWithValue(NonEmptyFlag): # E: Cannot inherit from final class "NonEmptyFlag"
1412-
x = 1
1445+
x = 1 # E: Cannot override final attribute "x" (previously declared in base class "NonEmptyFlag")
14131446
class ErrorIntFlagWithValue(NonEmptyIntFlag): # E: Cannot inherit from final class "NonEmptyIntFlag"
1414-
x = 1
1447+
x = 1 # E: Cannot override final attribute "x" (previously declared in base class "NonEmptyIntFlag")
14151448

14161449
class ErrorEnumWithoutValue(NonEmptyEnum): # E: Cannot inherit from final class "NonEmptyEnum"
14171450
pass

0 commit comments

Comments
 (0)