From 8eb4218631c384f831b3c2c9eae0be57d9d0ed38 Mon Sep 17 00:00:00 2001 From: "Terence D. Honles" Date: Thu, 2 May 2024 17:01:14 +0200 Subject: [PATCH 01/12] Fix enum attributes are not members This adds on to the change in https://github.com/python/mypy/pull/17182 and fixes enum attributes being used as members. --- mypy/semanal_enum.py | 6 ++++ mypy/typeops.py | 16 ++------- mypy/types.py | 9 ++++- test-data/unit/check-python310.test | 54 +++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 14 deletions(-) diff --git a/mypy/semanal_enum.py b/mypy/semanal_enum.py index 30e0bd56c3120..0094b719bc962 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 a59bd3739562a..568014b155e7b 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, @@ -878,18 +877,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 2cacc3e440850..cfd5edeef02e7 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1512,7 +1512,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/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 5ecc69dc7c324..78e55ef214bdf 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): From dfcc0c4b1a9b57b10f8d46ca3d1f6e843e273397 Mon Sep 17 00:00:00 2001 From: "Terence D. Honles" Date: Wed, 5 Jun 2024 06:54:48 -0700 Subject: [PATCH 02/12] add xfail test for unsupported enum nonmember --- test-data/unit/check-python311.test | 36 +++++++++++++++++++++++++++++ test-data/unit/lib-stub/enum.pyi | 3 +++ 2 files changed, 39 insertions(+) diff --git a/test-data/unit/check-python311.test b/test-data/unit/check-python311.test index 2d1a09ef33363..87d9beadae58f 100644 --- a/test-data/unit/check-python311.test +++ b/test-data/unit/check-python311.test @@ -1,3 +1,39 @@ +[case testMatchLiteralPatternEnumWithNonMember-xfail] +from enum import Enum, nonmember +from typing import NoReturn +def assert_never(x: NoReturn) -> None: ... + +class int: + def __new__(cls, value: int): pass + +class Medal(int, Enum): + prize: str = nonmember("nothing") + + def __new__(cls, value: int, prize: str | None) -> Medal: + enum = int.__new__(cls, value) + enum._value_ = value + if prize is not None: + 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 testTryStarSimple] try: pass diff --git a/test-data/unit/lib-stub/enum.pyi b/test-data/unit/lib-stub/enum.pyi index 11adfc5979552..3503954d8d928 100644 --- a/test-data/unit/lib-stub/enum.pyi +++ b/test-data/unit/lib-stub/enum.pyi @@ -48,3 +48,6 @@ class auto(IntFlag): # It is python-3.11+ only: class StrEnum(str, Enum): def __new__(cls: Type[_T], value: str | _T) -> _T: ... + + +def nonmember(value: _T) -> _T: pass From 50c9f31f3c458f9c3e99c165055f4288c496a60a Mon Sep 17 00:00:00 2001 From: "Terence D. Honles" Date: Wed, 5 Jun 2024 06:59:50 -0700 Subject: [PATCH 03/12] make "prize" actually default as was intended --- test-data/unit/check-python311.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-python311.test b/test-data/unit/check-python311.test index 87d9beadae58f..02460fa2e1215 100644 --- a/test-data/unit/check-python311.test +++ b/test-data/unit/check-python311.test @@ -9,7 +9,7 @@ class int: class Medal(int, Enum): prize: str = nonmember("nothing") - def __new__(cls, value: int, prize: str | None) -> Medal: + def __new__(cls, value: int, prize: str | None = None) -> Medal: enum = int.__new__(cls, value) enum._value_ = value if prize is not None: @@ -18,7 +18,7 @@ class Medal(int, Enum): gold = (1, 'cash prize') silver = (2, 'sponsorship') - bronze = (3, 'nothing') + bronze = (3,) m: Medal From 50d096783ecbae3a8966a6d041cee04f9743c59f Mon Sep 17 00:00:00 2001 From: "Terence D. Honles" Date: Tue, 6 Aug 2024 09:40:33 +0200 Subject: [PATCH 04/12] update: Type annotations are not allowed for enum members Co-authored-by: Ali Hamdan --- mypy/semanal.py | 3 +++ mypyc/test-data/run-classes.test | 4 ++-- test-data/unit/check-enum.test | 23 +++++++++++++++++- test-data/unit/check-python311.test | 36 ----------------------------- test-data/unit/pythoneval.test | 16 ++++++++++--- 5 files changed, 40 insertions(+), 42 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 782985e3fbab1..aec9b1cd89603 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -4213,6 +4213,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/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 59617714f7e73..7ebff8e8f3827 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 78a114eda7642..28bc76b19ead7 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-python311.test b/test-data/unit/check-python311.test index b55684f9efaee..28951824999f8 100644 --- a/test-data/unit/check-python311.test +++ b/test-data/unit/check-python311.test @@ -1,39 +1,3 @@ -[case testMatchLiteralPatternEnumWithNonMember-xfail] -from enum import Enum, nonmember -from typing import NoReturn -def assert_never(x: NoReturn) -> None: ... - -class int: - def __new__(cls, value: int): pass - -class Medal(int, Enum): - prize: str = nonmember("nothing") - - def __new__(cls, value: int, prize: str | None = None) -> Medal: - enum = int.__new__(cls, value) - enum._value_ = value - if prize is not None: - enum.prize = prize - return enum - - gold = (1, 'cash prize') - silver = (2, 'sponsorship') - bronze = (3,) - -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 testTryStarSimple] try: pass diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index b65a4cd59f797..2df6e2c18a9e0 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1601,14 +1601,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 From 7282eb88d6ce5fb16c69bb3189809e57f3bde0e7 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 26 Oct 2024 14:10:15 -0700 Subject: [PATCH 05/12] error wording --- mypy/semanal.py | 2 +- test-data/unit/check-enum.test | 8 ++++---- test-data/unit/pythoneval.test | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index f1dc6c7f39fb9..b0f098c686ca3 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -4236,7 +4236,7 @@ def analyze_name_lvalue( ) if explicit_type and has_explicit_value: - self.fail("Type annotations are not allowed for enum members", lvalue) + self.fail("Enum members must be left unannotated", lvalue) if (not existing or isinstance(existing.node, PlaceholderNode)) and not outer: # Define new variable. diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 28bc76b19ead7..4723cf6c1fded 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 # E: Type annotations are not allowed for enum members + x: int = 1 # E: Enum members must be left unannotated class B1(A1): # E: Cannot extend enum with existing members: "A1" pass @@ -2191,8 +2191,8 @@ def some_a(a: A): 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 \ + gold: int = 1 # E: Enum members must be left unannotated + silver: str = 2 # E: Enum members must be left unannotated \ # E: Incompatible types in assignment (expression has type "int", variable has type "str") bronze = 3 @@ -2202,7 +2202,7 @@ from enum import Enum class Pet(Enum): CAT = ... - DOG: str = ... # E: Type annotations are not allowed for enum members \ + DOG: str = ... # E: Enum members must be left unannotated \ # E: Incompatible types in assignment (expression has type "ellipsis", variable has type "str") [file test.pyi] diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 6ea2f581ce1e5..398cdf500c502 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1609,9 +1609,9 @@ reveal_type(Foo.Bar.value) # this should probably not be "Foo" https://typing.r 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: Enum members must be left unannotated _testEnumValueWithPlaceholderNodeType.py:5: 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: Enum members must be left unannotated _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" From 295343e21d10d6398c87a7a18a9bfb64706fdabd Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 26 Oct 2024 14:12:16 -0700 Subject: [PATCH 06/12] link to spec --- mypy/semanal.py | 4 ++++ test-data/unit/check-enum.test | 12 +++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index b0f098c686ca3..6c695c8c8df7d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -4237,6 +4237,10 @@ def analyze_name_lvalue( if explicit_type and has_explicit_value: self.fail("Enum members must be left unannotated", lvalue) + self.note( + "See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members", + lvalue, + ) if (not existing or isinstance(existing.node, PlaceholderNode)) and not outer: # Define new variable. diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 4723cf6c1fded..00d9123ff3b3a 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -1764,7 +1764,8 @@ class B(A): x = 1 # E: Cannot override writable attribute "x" with a final one class A1(Enum): - x: int = 1 # E: Enum members must be left unannotated + x: int = 1 # E: Enum members must be left unannotated \ + # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members class B1(A1): # E: Cannot extend enum with existing members: "A1" pass @@ -1779,6 +1780,7 @@ class A3(Enum): x: Final[int] # type: ignore class B3(A3): x = 1 # E: Cannot override final attribute "x" (previously declared in base class "A3") + [builtins fixtures/bool.pyi] [case testEnumNotFinalWithMethodsAndUninitializedValuesStub] @@ -2191,18 +2193,18 @@ def some_a(a: A): from enum import Enum class Medal(Enum): - gold: int = 1 # E: Enum members must be left unannotated + gold: int = 1 # E: Enum members must be left unannotated \ + # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members silver: str = 2 # E: Enum members must be left unannotated \ + # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-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: Enum members must be left unannotated \ + # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members \ # E: Incompatible types in assignment (expression has type "ellipsis", variable has type "str") - -[file test.pyi] From 2afee12f88c3ddfc29a72e9bf197e91ab7721d30 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 26 Oct 2024 14:15:51 -0700 Subject: [PATCH 07/12] move test --- test-data/unit/check-enum.test | 20 ++++++++++++++++++++ test-data/unit/pythoneval.test | 22 ---------------------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 00d9123ff3b3a..41bd708db0e8b 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2208,3 +2208,23 @@ class Pet(Enum): DOG: str = ... # E: Enum members must be left unannotated \ # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members \ # E: Incompatible types in assignment (expression has type "ellipsis", variable has type "str") + +[case testEnumValueWithPlaceholderNodeType] +# https://github.com/python/mypy/issues/11971 +from enum import Enum +from typing import Any, Callable, Dict +class Foo(Enum): + Bar: Foo = Callable[[str], None] # E: Enum members must be left unannotated \ + # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members \ + # E: Value of type "int" is not indexable + Baz: Any = Callable[[Dict[str, "Missing"]], None] # E: Enum members must be left unannotated \ + # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members \ + # E: Value of type "int" is not indexable \ + # E: Type application targets a non-generic function or class \ + # E: Name "Missing" is not defined + +reveal_type(Foo.Bar) # N: Revealed type is "Literal[__main__.Foo.Bar]?" +reveal_type(Foo.Bar.value) # N: Revealed type is "__main__.Foo" +reveal_type(Foo.Baz) # N: Revealed type is "Literal[__main__.Foo.Baz]?" +reveal_type(Foo.Baz.value) # N: Revealed type is "Any" +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 398cdf500c502..e73dbf79a0ec6 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1596,28 +1596,6 @@ if isinstance(obj, Awaitable): _testSpecialTypingProtocols.py:6: note: Revealed type is "Tuple[builtins.int]" _testSpecialTypingProtocols.py:8: error: Statement is unreachable -[case testEnumValueWithPlaceholderNodeType] -# https://github.com/python/mypy/issues/11971 -from enum import Enum -from typing import Any, Callable, Dict -class Foo(Enum): - Bar: Foo = Callable[[str], 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: Enum members must be left unannotated -_testEnumValueWithPlaceholderNodeType.py:5: error: Incompatible types in assignment (expression has type "", variable has type "Foo") -_testEnumValueWithPlaceholderNodeType.py:6: error: Enum members must be left unannotated -_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 From 46527644f5ad95de7ef7d86d10f013a609b12e48 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sat, 26 Oct 2024 14:18:10 -0700 Subject: [PATCH 08/12] remove spurious int error --- test-data/unit/check-enum.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 41bd708db0e8b..520be02616728 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2216,10 +2216,9 @@ from typing import Any, Callable, Dict class Foo(Enum): Bar: Foo = Callable[[str], None] # E: Enum members must be left unannotated \ # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members \ - # E: Value of type "int" is not indexable + # E: Incompatible types in assignment (expression has type "", variable has type "Foo") Baz: Any = Callable[[Dict[str, "Missing"]], None] # E: Enum members must be left unannotated \ # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members \ - # E: Value of type "int" is not indexable \ # E: Type application targets a non-generic function or class \ # E: Name "Missing" is not defined @@ -2228,3 +2227,4 @@ reveal_type(Foo.Bar.value) # N: Revealed type is "__main__.Foo" reveal_type(Foo.Baz) # N: Revealed type is "Literal[__main__.Foo.Baz]?" reveal_type(Foo.Baz.value) # N: Revealed type is "Any" [builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] From 4e3efbcd11f85241eadee881d0939ff47cb0a45a Mon Sep 17 00:00:00 2001 From: "Terence D. Honles" Date: Mon, 28 Oct 2024 09:25:37 +0100 Subject: [PATCH 09/12] add test case --- test-data/unit/check-enum.test | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 520be02616728..b1f3e7de3f165 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2228,3 +2228,27 @@ reveal_type(Foo.Baz) # N: Revealed type is "Literal[__main__.Foo.Baz]?" reveal_type(Foo.Baz.value) # N: Revealed type is "Any" [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] + + +[case testEnumWithAnnotationOnly] +# flags: --warn-unreachable +import enum + + +class E(enum.IntEnum): + A: int # E: Enum members must be left unannotated \ + # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members + B: int # E: Enum members must be left unannotated \ + # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members + + +def do_check(value: E) -> None: + reveal_type(value) # N: Revealed type is "__main__.E" + if value is E.A: + return + + reveal_type(value) # N: Revealed type is "Literal[__main__.E.B]" + "should be reachable" + +[builtins fixtures/primitives.pyi] +[typing fixtures/typing-full.pyi] From d68b5d1a8c6476d690be4569b51997be574e44be Mon Sep 17 00:00:00 2001 From: "Terence D. Honles" Date: Mon, 28 Oct 2024 12:42:53 +0100 Subject: [PATCH 10/12] adjust test case and fix enums without members being collapsed to never which is also the base of all types --- mypy/typeops.py | 14 ++++++-------- test-data/unit/check-enum.test | 10 ++++------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index ddcd4e1961d65..fbaffd952e8de 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -897,18 +897,16 @@ class Status(Enum): items = [ try_expanding_sum_type_to_union(item, target_fullname) for item in typ.relevant_items() ] - return make_simplified_union(items, contract_literals=False) elif isinstance(typ, Instance) and typ.type.fullname == target_fullname: if typ.type.is_enum: - return make_simplified_union( - [LiteralType(name, typ) for name in typ.get_enum_values()], contract_literals=False - ) + items = [LiteralType(name, typ) for name in typ.get_enum_values()] elif typ.type.fullname == "builtins.bool": - return make_simplified_union( - [LiteralType(True, typ), LiteralType(False, typ)], contract_literals=False - ) + items = [LiteralType(True, typ), LiteralType(False, typ)] + else: + return typ - return typ + # if the expanded union would be `Never` leave the type as is + return typ if not items else make_simplified_union(items, contract_literals=False) def try_contracting_literals_in_union(types: Sequence[Type]) -> list[ProperType]: diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index b1f3e7de3f165..4b7df6a243912 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2230,16 +2230,14 @@ reveal_type(Foo.Baz.value) # N: Revealed type is "Any" [typing fixtures/typing-full.pyi] -[case testEnumWithAnnotationOnly] +[case testEnumWithImplicitMembersUsingAnnotationOnly] # flags: --warn-unreachable import enum class E(enum.IntEnum): - A: int # E: Enum members must be left unannotated \ - # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members - B: int # E: Enum members must be left unannotated \ - # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members + A: int + B: int def do_check(value: E) -> None: @@ -2247,7 +2245,7 @@ def do_check(value: E) -> None: if value is E.A: return - reveal_type(value) # N: Revealed type is "Literal[__main__.E.B]" + reveal_type(value) # N: Revealed type is "__main__.E" "should be reachable" [builtins fixtures/primitives.pyi] From 83c48a2c4c5a00eff6e894054c854b94b103e3bf Mon Sep 17 00:00:00 2001 From: "Terence D. Honles" Date: Mon, 28 Oct 2024 12:54:34 +0100 Subject: [PATCH 11/12] change test name and add comment --- test-data/unit/check-enum.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 4b7df6a243912..0eb796ae551e0 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2230,7 +2230,7 @@ reveal_type(Foo.Baz.value) # N: Revealed type is "Any" [typing fixtures/typing-full.pyi] -[case testEnumWithImplicitMembersUsingAnnotationOnly] +[case testEnumWithOnlyImplicitMembersUsingAnnotationOnly] # flags: --warn-unreachable import enum @@ -2242,7 +2242,7 @@ class E(enum.IntEnum): def do_check(value: E) -> None: reveal_type(value) # N: Revealed type is "__main__.E" - if value is E.A: + if value is E.A: # this is a nonmember check, not an emum member check which should narrow the value return reveal_type(value) # N: Revealed type is "__main__.E" From eb9f87c6c0715d83ff47425543fcd9f268a48306 Mon Sep 17 00:00:00 2001 From: "Terence D. Honles" Date: Mon, 28 Oct 2024 12:56:15 +0100 Subject: [PATCH 12/12] update comment --- test-data/unit/check-enum.test | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 0eb796ae551e0..09e2abb303583 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2242,7 +2242,8 @@ class E(enum.IntEnum): def do_check(value: E) -> None: reveal_type(value) # N: Revealed type is "__main__.E" - if value is E.A: # this is a nonmember check, not an emum member check which should narrow the value + # this is a nonmember check, not an emum member check, and it should not narrow the value + if value is E.A: return reveal_type(value) # N: Revealed type is "__main__.E"