From abdb5140ef0a7dda01f677584b8a39bc56e71084 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 21 Jan 2024 17:38:30 -0800 Subject: [PATCH 1/2] Error for assignment of functional Enum to variable of different name Relates to discussion in https://discuss.python.org/t/draft-of-typing-spec-chapter-for-enums/43496/11 --- mypy/semanal_enum.py | 17 ++++++--- test-data/unit/check-enum.test | 70 +++++++++++++--------------------- 2 files changed, 38 insertions(+), 49 deletions(-) diff --git a/mypy/semanal_enum.py b/mypy/semanal_enum.py index 21576ab47a846..ef319c0ce3882 100644 --- a/mypy/semanal_enum.py +++ b/mypy/semanal_enum.py @@ -103,7 +103,8 @@ 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 @@ -111,6 +112,10 @@ class A(enum.Enum): 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. @@ -142,7 +147,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. @@ -172,6 +177,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): @@ -239,13 +246,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 diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 6779ae2664540..895261efb1d47 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -452,55 +452,37 @@ 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" + [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 @@ -1117,7 +1099,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" From ee271c532eb206b13c802cd861449dae6c6db301 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 21 Jan 2024 17:41:15 -0800 Subject: [PATCH 2/2] error --- mypy/semanal_enum.py | 4 +++- test-data/unit/check-enum.test | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mypy/semanal_enum.py b/mypy/semanal_enum.py index ef319c0ce3882..30e0bd56c3120 100644 --- a/mypy/semanal_enum.py +++ b/mypy/semanal_enum.py @@ -104,7 +104,9 @@ class A(enum.Enum): if fullname not in ENUM_BASES: return None - new_class_name, 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 diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 895261efb1d47..b4e8795859c33 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -481,6 +481,8 @@ V = Enum('U', **{'a': 1}) # E: Unexpected arguments to Enum() W = Enum('W', 'a b') 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[__main__.Something@23.a]?" +X.asdf # E: "Type[Something@23]" has no attribute "asdf" [typing fixtures/typing-medium.pyi]