Skip to content

Commit df35dcf

Browse files
authored
Error for assignment of functional Enum to variable of different name (#16805)
Relates to discussion in https://discuss.python.org/t/draft-of-typing-spec-chapter-for-enums/43496/11
1 parent 0570f71 commit df35dcf

File tree

2 files changed

+42
-49
lines changed

2 files changed

+42
-49
lines changed

mypy/semanal_enum.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,21 @@ class A(enum.Enum):
103103
fullname = callee.fullname
104104
if fullname not in ENUM_BASES:
105105
return None
106-
items, values, ok = self.parse_enum_call_args(call, fullname.split(".")[-1])
106+
107+
new_class_name, items, values, ok = self.parse_enum_call_args(
108+
call, fullname.split(".")[-1]
109+
)
107110
if not ok:
108111
# Error. Construct dummy return value.
109112
name = var_name
110113
if is_func_scope:
111114
name += "@" + str(call.line)
112115
info = self.build_enum_call_typeinfo(name, [], fullname, node.line)
113116
else:
117+
if new_class_name != var_name:
118+
msg = f'String argument 1 "{new_class_name}" to {fullname}(...) does not match variable name "{var_name}"'
119+
self.fail(msg, call)
120+
114121
name = cast(StrExpr, call.args[0]).value
115122
if name != var_name or is_func_scope:
116123
# Give it a unique name derived from the line number.
@@ -142,7 +149,7 @@ def build_enum_call_typeinfo(
142149

143150
def parse_enum_call_args(
144151
self, call: CallExpr, class_name: str
145-
) -> tuple[list[str], list[Expression | None], bool]:
152+
) -> tuple[str, list[str], list[Expression | None], bool]:
146153
"""Parse arguments of an Enum call.
147154
148155
Return a tuple of fields, values, was there an error.
@@ -172,6 +179,8 @@ def parse_enum_call_args(
172179
return self.fail_enum_call_arg(
173180
f"{class_name}() expects a string literal as the first argument", call
174181
)
182+
new_class_name = value.value
183+
175184
items = []
176185
values: list[Expression | None] = []
177186
if isinstance(names, StrExpr):
@@ -239,13 +248,13 @@ def parse_enum_call_args(
239248
if not values:
240249
values = [None] * len(items)
241250
assert len(items) == len(values)
242-
return items, values, True
251+
return new_class_name, items, values, True
243252

244253
def fail_enum_call_arg(
245254
self, message: str, context: Context
246-
) -> tuple[list[str], list[Expression | None], bool]:
255+
) -> tuple[str, list[str], list[Expression | None], bool]:
247256
self.fail(message, context)
248-
return [], [], False
257+
return "", [], [], False
249258

250259
# Helpers
251260

test-data/unit/check-enum.test

Lines changed: 28 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -452,55 +452,39 @@ from enum import Enum, IntEnum
452452

453453
PictureSize = Enum('PictureSize', 'P0 P1 P2 P3 P4 P5 P6 P7 P8', type=str, module=__name__)
454454
fake_enum1 = Enum('fake_enum1', ['a', 'b'])
455-
fake_enum2 = Enum('fake_enum1', names=['a', 'b'])
456-
fake_enum3 = Enum(value='fake_enum1', names=['a', 'b'])
457-
fake_enum4 = Enum(value='fake_enum1', names=['a', 'b'] , module=__name__)
455+
fake_enum2 = Enum('fake_enum2', names=['a', 'b'])
456+
fake_enum3 = Enum(value='fake_enum3', names=['a', 'b'])
457+
fake_enum4 = Enum(value='fake_enum4', names=['a', 'b'] , module=__name__)
458458

459459
[case testFunctionalEnumErrors]
460460
from enum import Enum, IntEnum
461-
A = Enum('A')
462-
B = Enum('B', 42)
463-
C = Enum('C', 'a b', 'x', 'y', 'z', 'p', 'q')
464-
D = Enum('D', foo)
461+
A = Enum('A') # E: Too few arguments for Enum()
462+
B = Enum('B', 42) # E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members
463+
C = Enum('C', 'a b', 'x', 'y', 'z', 'p', 'q') # E: Too many arguments for Enum()
464+
D = Enum('D', foo) # E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members \
465+
# E: Name "foo" is not defined
465466
bar = 'x y z'
466-
E = Enum('E', bar)
467-
I = IntEnum('I')
468-
J = IntEnum('I', 42)
469-
K = IntEnum('I', 'p q', 'x', 'y', 'z', 'p', 'q')
470-
L = Enum('L', ' ')
471-
M = Enum('M', ())
472-
N = IntEnum('M', [])
473-
P = Enum('P', [42])
474-
Q = Enum('Q', [('a', 42, 0)])
475-
R = IntEnum('R', [[0, 42]])
476-
S = Enum('S', {1: 1})
477-
T = Enum('T', keyword='a b')
478-
U = Enum('U', *['a'])
479-
V = Enum('U', **{'a': 1})
467+
E = Enum('E', bar) # E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members
468+
I = IntEnum('I') # E: Too few arguments for IntEnum()
469+
J = IntEnum('I', 42) # E: Second argument of IntEnum() must be string, tuple, list or dict literal for mypy to determine Enum members
470+
K = IntEnum('I', 'p q', 'x', 'y', 'z', 'p', 'q') # E: Too many arguments for IntEnum()
471+
L = Enum('L', ' ') # E: Enum() needs at least one item
472+
M = Enum('M', ()) # E: Enum() needs at least one item
473+
N = IntEnum('M', []) # E: IntEnum() needs at least one item
474+
P = Enum('P', [42]) # E: Enum() with tuple or list expects strings or (name, value) pairs
475+
Q = Enum('Q', [('a', 42, 0)]) # E: Enum() with tuple or list expects strings or (name, value) pairs
476+
R = IntEnum('R', [[0, 42]]) # E: IntEnum() with tuple or list expects strings or (name, value) pairs
477+
S = Enum('S', {1: 1}) # E: Enum() with dict literal requires string literals
478+
T = Enum('T', keyword='a b') # E: Unexpected keyword argument "keyword"
479+
U = Enum('U', *['a']) # E: Unexpected arguments to Enum()
480+
V = Enum('U', **{'a': 1}) # E: Unexpected arguments to Enum()
480481
W = Enum('W', 'a b')
481-
W.c
482+
W.c # E: "Type[W]" has no attribute "c"
483+
X = Enum('Something', 'a b') # E: String argument 1 "Something" to enum.Enum(...) does not match variable name "X"
484+
reveal_type(X.a) # N: Revealed type is "Literal[[email protected]]?"
485+
X.asdf # E: "Type[Something@23]" has no attribute "asdf"
486+
482487
[typing fixtures/typing-medium.pyi]
483-
[out]
484-
main:2: error: Too few arguments for Enum()
485-
main:3: error: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members
486-
main:4: error: Too many arguments for Enum()
487-
main:5: error: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members
488-
main:5: error: Name "foo" is not defined
489-
main:7: error: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members
490-
main:8: error: Too few arguments for IntEnum()
491-
main:9: error: Second argument of IntEnum() must be string, tuple, list or dict literal for mypy to determine Enum members
492-
main:10: error: Too many arguments for IntEnum()
493-
main:11: error: Enum() needs at least one item
494-
main:12: error: Enum() needs at least one item
495-
main:13: error: IntEnum() needs at least one item
496-
main:14: error: Enum() with tuple or list expects strings or (name, value) pairs
497-
main:15: error: Enum() with tuple or list expects strings or (name, value) pairs
498-
main:16: error: IntEnum() with tuple or list expects strings or (name, value) pairs
499-
main:17: error: Enum() with dict literal requires string literals
500-
main:18: error: Unexpected keyword argument "keyword"
501-
main:19: error: Unexpected arguments to Enum()
502-
main:20: error: Unexpected arguments to Enum()
503-
main:22: error: "Type[W]" has no attribute "c"
504488

505489
[case testFunctionalEnumFlag]
506490
from enum import Flag, IntFlag
@@ -1117,7 +1101,7 @@ from enum import Enum
11171101

11181102
class A:
11191103
def __init__(self) -> None:
1120-
self.b = Enum("x", [("foo", "bar")]) # E: Enum type as attribute is not supported
1104+
self.b = Enum("b", [("foo", "bar")]) # E: Enum type as attribute is not supported
11211105

11221106
reveal_type(A().b) # N: Revealed type is "Any"
11231107

0 commit comments

Comments
 (0)