diff --git a/mypy/semanal.py b/mypy/semanal.py index 780d0b614ae3..6df4128d8687 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -4220,6 +4220,9 @@ def analyze_name_lvalue( lvalue, ) + if explicit_type and has_explicit_value: + self.fail("Type annotations are not allowed for enum members", lvalue) + if (not existing or isinstance(existing.node, PlaceholderNode)) and not outer: # Define new variable. var = self.make_name_lvalue_var(lvalue, kind, not explicit_type, has_explicit_value) diff --git a/mypy/semanal_enum.py b/mypy/semanal_enum.py index 30e0bd56c312..0094b719bc96 100644 --- a/mypy/semanal_enum.py +++ b/mypy/semanal_enum.py @@ -143,6 +143,12 @@ def build_enum_call_typeinfo( var = Var(item) var.info = info var.is_property = True + # When an enum is created by its functional form `Enum(name, values)` + # - if it is a string it is first split by commas/whitespace + # - if it is an iterable of single items each item is assigned a value starting at `start` + # - if it is an iterable of (name, value) then the given values will be used + # either way, each item should be treated as if it has an explicit value. + var.has_explicit_value = True var._fullname = f"{info.fullname}.{item}" info.names[item] = SymbolTableNode(MDEF, var) return info diff --git a/mypy/typeops.py b/mypy/typeops.py index d22448a715e5..ddcd4e1961d6 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -30,7 +30,6 @@ ) from mypy.state import state from mypy.types import ( - ENUM_REMOVED_PROPS, AnyType, CallableType, ExtraAttrs, @@ -901,18 +900,9 @@ class Status(Enum): return make_simplified_union(items, contract_literals=False) elif isinstance(typ, Instance) and typ.type.fullname == target_fullname: if typ.type.is_enum: - new_items = [] - for name, symbol in typ.type.names.items(): - if not isinstance(symbol.node, Var): - continue - # Skip these since Enum will remove it - if name in ENUM_REMOVED_PROPS: - continue - # Skip private attributes - if name.startswith("__"): - continue - new_items.append(LiteralType(name, typ)) - return make_simplified_union(new_items, contract_literals=False) + return make_simplified_union( + [LiteralType(name, typ) for name in typ.get_enum_values()], contract_literals=False + ) elif typ.type.fullname == "builtins.bool": return make_simplified_union( [LiteralType(True, typ), LiteralType(False, typ)], contract_literals=False diff --git a/mypy/types.py b/mypy/types.py index 78244d0f9cf4..67ede842a7e2 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1550,7 +1550,14 @@ def is_singleton_type(self) -> bool: def get_enum_values(self) -> list[str]: """Return the list of values for an Enum.""" return [ - name for name, sym in self.type.names.items() if isinstance(sym.node, mypy.nodes.Var) + name + for name, sym in self.type.names.items() + if ( + isinstance(sym.node, mypy.nodes.Var) + and name not in ENUM_REMOVED_PROPS + and not name.startswith("__") + and sym.node.has_explicit_value + ) ] diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 59617714f7e7..7ebff8e8f382 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -254,8 +254,8 @@ from enum import Enum class TestEnum(Enum): _order_ = "a b" - a : int = 1 - b : int = 2 + a = 1 + b = 2 @classmethod def test(cls) -> int: diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 78a114eda764..28bc76b19ead 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -1764,7 +1764,7 @@ class B(A): x = 1 # E: Cannot override writable attribute "x" with a final one class A1(Enum): - x: int = 1 + x: int = 1 # E: Type annotations are not allowed for enum members class B1(A1): # E: Cannot extend enum with existing members: "A1" pass @@ -2185,3 +2185,24 @@ reveal_type(A.y.value) # N: Revealed type is "Literal[2]?" def some_a(a: A): reveal_type(a.value) # N: Revealed type is "Union[Literal[1]?, Literal[2]?]" [builtins fixtures/dict.pyi] + + +[case testErrorOnAnnotatedMember] +from enum import Enum + +class Medal(Enum): + gold: int = 1 # E: Type annotations are not allowed for enum members + silver: str = 2 # E: Type annotations are not allowed for enum members \ + # E: Incompatible types in assignment (expression has type "int", variable has type "str") + bronze = 3 + + +[case testEnumMemberWithPlaceholder] +from enum import Enum + +class Pet(Enum): + CAT = ... + DOG: str = ... # E: Type annotations are not allowed for enum members \ + # E: Incompatible types in assignment (expression has type "ellipsis", variable has type "str") + +[file test.pyi] diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 5ecc69dc7c32..78e55ef214bd 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -1499,6 +1499,60 @@ def g(m: Medal) -> int: reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.bronze]" return 2 + +[case testMatchLiteralPatternEnumWithTypedAttribute] +from enum import Enum +from typing import NoReturn +def assert_never(x: NoReturn) -> None: ... + +class int: + def __new__(cls, value: int): pass + +class Medal(int, Enum): + prize: str + + def __new__(cls, value: int, prize: str) -> Medal: + enum = int.__new__(cls, value) + enum._value_ = value + enum.prize = prize + return enum + + gold = (1, 'cash prize') + silver = (2, 'sponsorship') + bronze = (3, 'nothing') + +m: Medal + +match m: + case Medal.gold: + reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.gold]" + case Medal.silver: + reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.silver]" + case Medal.bronze: + reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.bronze]" + case _ as unreachable: + assert_never(unreachable) + +[builtins fixtures/tuple.pyi] + +[case testMatchLiteralPatternFunctionalEnum] +from enum import Enum +from typing import NoReturn +def assert_never(x: NoReturn) -> None: ... + +Medal = Enum('Medal', 'gold silver bronze') +m: Medal + +match m: + case Medal.gold: + reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.gold]" + case Medal.silver: + reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.silver]" + case Medal.bronze: + reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.bronze]" + case _ as unreachable: + assert_never(unreachable) + [case testMatchLiteralPatternEnumCustomEquals-skip] from enum import Enum class Medal(Enum): diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index dbf228623d7c..a2a23eeea245 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1599,14 +1599,24 @@ _testSpecialTypingProtocols.py:8: error: Statement is unreachable [case testEnumValueWithPlaceholderNodeType] # https://github.com/python/mypy/issues/11971 from enum import Enum -from typing import Callable, Dict +from typing import Any, Callable, Dict class Foo(Enum): Bar: Foo = Callable[[str], None] - Baz: Foo = Callable[[Dict[str, "Missing"]], None] + Baz: Any = Callable[[Dict[str, "Missing"]], None] + +reveal_type(Foo.Bar) +reveal_type(Foo.Bar.value) # this should probably not be "Foo" https://typing.readthedocs.io/en/latest/spec/enums.html#member-values +reveal_type(Foo.Baz) +reveal_type(Foo.Baz.value) [out] +_testEnumValueWithPlaceholderNodeType.py:5: error: Type annotations are not allowed for enum members _testEnumValueWithPlaceholderNodeType.py:5: error: Incompatible types in assignment (expression has type "", variable has type "Foo") -_testEnumValueWithPlaceholderNodeType.py:6: error: Incompatible types in assignment (expression has type "", variable has type "Foo") +_testEnumValueWithPlaceholderNodeType.py:6: error: Type annotations are not allowed for enum members _testEnumValueWithPlaceholderNodeType.py:6: error: Name "Missing" is not defined +_testEnumValueWithPlaceholderNodeType.py:8: note: Revealed type is "Literal[_testEnumValueWithPlaceholderNodeType.Foo.Bar]?" +_testEnumValueWithPlaceholderNodeType.py:9: note: Revealed type is "_testEnumValueWithPlaceholderNodeType.Foo" +_testEnumValueWithPlaceholderNodeType.py:10: note: Revealed type is "Literal[_testEnumValueWithPlaceholderNodeType.Foo.Baz]?" +_testEnumValueWithPlaceholderNodeType.py:11: note: Revealed type is "Any" [case testTypeshedRecursiveTypesExample] from typing import List, Union