Skip to content

Commit

Permalink
Error for assignment of functional Enum to variable of different name (
Browse files Browse the repository at this point in the history
  • Loading branch information
hauntsaninja authored Apr 17, 2024
1 parent 0570f71 commit df35dcf
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 49 deletions.
19 changes: 14 additions & 5 deletions mypy/semanal_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,21 @@ class A(enum.Enum):
fullname = callee.fullname
if fullname not in ENUM_BASES:
return None
items, values, ok = self.parse_enum_call_args(call, fullname.split(".")[-1])

new_class_name, items, values, ok = self.parse_enum_call_args(
call, fullname.split(".")[-1]
)
if not ok:
# Error. Construct dummy return value.
name = var_name
if is_func_scope:
name += "@" + str(call.line)
info = self.build_enum_call_typeinfo(name, [], fullname, node.line)
else:
if new_class_name != var_name:
msg = f'String argument 1 "{new_class_name}" to {fullname}(...) does not match variable name "{var_name}"'
self.fail(msg, call)

name = cast(StrExpr, call.args[0]).value
if name != var_name or is_func_scope:
# Give it a unique name derived from the line number.
Expand Down Expand Up @@ -142,7 +149,7 @@ def build_enum_call_typeinfo(

def parse_enum_call_args(
self, call: CallExpr, class_name: str
) -> tuple[list[str], list[Expression | None], bool]:
) -> tuple[str, list[str], list[Expression | None], bool]:
"""Parse arguments of an Enum call.
Return a tuple of fields, values, was there an error.
Expand Down Expand Up @@ -172,6 +179,8 @@ def parse_enum_call_args(
return self.fail_enum_call_arg(
f"{class_name}() expects a string literal as the first argument", call
)
new_class_name = value.value

items = []
values: list[Expression | None] = []
if isinstance(names, StrExpr):
Expand Down Expand Up @@ -239,13 +248,13 @@ def parse_enum_call_args(
if not values:
values = [None] * len(items)
assert len(items) == len(values)
return items, values, True
return new_class_name, items, values, True

def fail_enum_call_arg(
self, message: str, context: Context
) -> tuple[list[str], list[Expression | None], bool]:
) -> tuple[str, list[str], list[Expression | None], bool]:
self.fail(message, context)
return [], [], False
return "", [], [], False

# Helpers

Expand Down
72 changes: 28 additions & 44 deletions test-data/unit/check-enum.test
Original file line number Diff line number Diff line change
Expand Up @@ -452,55 +452,39 @@ from enum import Enum, IntEnum

PictureSize = Enum('PictureSize', 'P0 P1 P2 P3 P4 P5 P6 P7 P8', type=str, module=__name__)
fake_enum1 = Enum('fake_enum1', ['a', 'b'])
fake_enum2 = Enum('fake_enum1', names=['a', 'b'])
fake_enum3 = Enum(value='fake_enum1', names=['a', 'b'])
fake_enum4 = Enum(value='fake_enum1', names=['a', 'b'] , module=__name__)
fake_enum2 = Enum('fake_enum2', names=['a', 'b'])
fake_enum3 = Enum(value='fake_enum3', names=['a', 'b'])
fake_enum4 = Enum(value='fake_enum4', names=['a', 'b'] , module=__name__)

[case testFunctionalEnumErrors]
from enum import Enum, IntEnum
A = Enum('A')
B = Enum('B', 42)
C = Enum('C', 'a b', 'x', 'y', 'z', 'p', 'q')
D = Enum('D', foo)
A = Enum('A') # E: Too few arguments for Enum()
B = Enum('B', 42) # E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members
C = Enum('C', 'a b', 'x', 'y', 'z', 'p', 'q') # E: Too many arguments for Enum()
D = Enum('D', foo) # E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members \
# E: Name "foo" is not defined
bar = 'x y z'
E = Enum('E', bar)
I = IntEnum('I')
J = IntEnum('I', 42)
K = IntEnum('I', 'p q', 'x', 'y', 'z', 'p', 'q')
L = Enum('L', ' ')
M = Enum('M', ())
N = IntEnum('M', [])
P = Enum('P', [42])
Q = Enum('Q', [('a', 42, 0)])
R = IntEnum('R', [[0, 42]])
S = Enum('S', {1: 1})
T = Enum('T', keyword='a b')
U = Enum('U', *['a'])
V = Enum('U', **{'a': 1})
E = Enum('E', bar) # E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members
I = IntEnum('I') # E: Too few arguments for IntEnum()
J = IntEnum('I', 42) # E: Second argument of IntEnum() must be string, tuple, list or dict literal for mypy to determine Enum members
K = IntEnum('I', 'p q', 'x', 'y', 'z', 'p', 'q') # E: Too many arguments for IntEnum()
L = Enum('L', ' ') # E: Enum() needs at least one item
M = Enum('M', ()) # E: Enum() needs at least one item
N = IntEnum('M', []) # E: IntEnum() needs at least one item
P = Enum('P', [42]) # E: Enum() with tuple or list expects strings or (name, value) pairs
Q = Enum('Q', [('a', 42, 0)]) # E: Enum() with tuple or list expects strings or (name, value) pairs
R = IntEnum('R', [[0, 42]]) # E: IntEnum() with tuple or list expects strings or (name, value) pairs
S = Enum('S', {1: 1}) # E: Enum() with dict literal requires string literals
T = Enum('T', keyword='a b') # E: Unexpected keyword argument "keyword"
U = Enum('U', *['a']) # E: Unexpected arguments to Enum()
V = Enum('U', **{'a': 1}) # E: Unexpected arguments to Enum()
W = Enum('W', 'a b')
W.c
W.c # E: "Type[W]" has no attribute "c"
X = Enum('Something', 'a b') # E: String argument 1 "Something" to enum.Enum(...) does not match variable name "X"
reveal_type(X.a) # N: Revealed type is "Literal[[email protected]]?"
X.asdf # E: "Type[Something@23]" has no attribute "asdf"

[typing fixtures/typing-medium.pyi]
[out]
main:2: error: Too few arguments for Enum()
main:3: error: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members
main:4: error: Too many arguments for Enum()
main:5: error: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members
main:5: error: Name "foo" is not defined
main:7: error: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members
main:8: error: Too few arguments for IntEnum()
main:9: error: Second argument of IntEnum() must be string, tuple, list or dict literal for mypy to determine Enum members
main:10: error: Too many arguments for IntEnum()
main:11: error: Enum() needs at least one item
main:12: error: Enum() needs at least one item
main:13: error: IntEnum() needs at least one item
main:14: error: Enum() with tuple or list expects strings or (name, value) pairs
main:15: error: Enum() with tuple or list expects strings or (name, value) pairs
main:16: error: IntEnum() with tuple or list expects strings or (name, value) pairs
main:17: error: Enum() with dict literal requires string literals
main:18: error: Unexpected keyword argument "keyword"
main:19: error: Unexpected arguments to Enum()
main:20: error: Unexpected arguments to Enum()
main:22: error: "Type[W]" has no attribute "c"

[case testFunctionalEnumFlag]
from enum import Flag, IntFlag
Expand Down Expand Up @@ -1117,7 +1101,7 @@ from enum import Enum

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

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

Expand Down

0 comments on commit df35dcf

Please sign in to comment.