From f0c5e37e1ac1acfedddf399778f0b928e5e05e5b Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 28 Jan 2025 15:31:14 +0300 Subject: [PATCH 1/4] Properly account for `member` and `nonmember` in `TypeInfo.enum_members` --- mypy/nodes.py | 18 ++++++++++++---- mypy/typeops.py | 2 +- test-data/unit/check-enum.test | 39 ++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 9364805d44d4..609264560b07 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -3235,10 +3235,20 @@ def enum_members(self) -> list[str]: name for name, sym in self.names.items() if ( - isinstance(sym.node, Var) - and name not in EXCLUDED_ENUM_ATTRIBUTES - and not name.startswith("__") - and sym.node.has_explicit_value + ( + isinstance(sym.node, Var) + and name not in EXCLUDED_ENUM_ATTRIBUTES + and not name.startswith("__") + and sym.node.has_explicit_value + and not (isinstance(typ := mypy.types.get_proper_type(sym.node.type), mypy.types.Instance) and typ.type.fullname == "enum.nonmember") + ) or ( + isinstance(sym.node, Decorator) + and any( + dec.fullname == "enum.member" + for dec in sym.node.decorators + if isinstance(dec, RefExpr) + ) + ) ) ] diff --git a/mypy/typeops.py b/mypy/typeops.py index 1667e8431a17..ac0695a096a6 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -955,7 +955,7 @@ class Status(Enum): FAILURE = 2 UNKNOWN = 3 - ...and if we call `try_expanding_enum_to_union(Union[Color, Status], 'module.Color')`, + ...and if we call `try_expanding_sum_type_to_union(Union[Color, Status], 'module.Color')`, this function will return Literal[Color.RED, Color.BLUE, Color.YELLOW, Status]. """ typ = get_proper_type(typ) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 37c63f43179d..3a4fc5fa68fd 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -1938,6 +1938,23 @@ class D(C): # E: Cannot extend enum with existing members: "C" \ x: int # E: Cannot assign to final name "x" [builtins fixtures/bool.pyi] +[case testEnumNotFinalWithMethodsAndUninitializedValuesStubMemberAndNonMember] +# flags: --python-version 3.11 +# This was added in 3.11 +import lib + +[file lib.pyi] +from enum import Enum, member, nonmember + +class A(Enum): # E: Detected enum "lib.A" in a type stub with zero members. There is a chance this is due to a recent change in the semantics of enum membership. If so, use `member = value` to mark an enum member, instead of `member: type` \ + # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members + x = nonmember(1) + +class B(Enum): + @member + def x(self) -> None: ... +[builtins fixtures/bool.pyi] + [case testEnumLiteralValues] from enum import Enum @@ -2330,6 +2347,28 @@ def some_a(a: A): [builtins fixtures/dict.pyi] +[case testEnumMemberAndNonMemberSupport] +# flags: --python-version 3.11 --warn-unreachable +# This was added in 3.11 +from enum import Enum, member, nonmember + +class A(Enum): + x = 1 + y = member(2) + z = nonmember(3) + +def some_a(a: A): + if a is not A.x and a is not A.z: + reveal_type(a) # N: Revealed type is "Literal[__main__.A.y]" + if a is not A.y and a is not A.z: + reveal_type(a) # N: Revealed type is "Literal[__main__.A.x]" + if a is not A.x: + reveal_type(a) # N: Revealed type is "Literal[__main__.A.y]" + if a is not A.y: + reveal_type(a) # N: Revealed type is "Literal[__main__.A.x]" +[builtins fixtures/dict.pyi] + + [case testErrorOnAnnotatedMember] from enum import Enum From 59552103bddaa6b026f58a01071aab61ea9a9952 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 12:33:30 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/nodes.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 609264560b07..bc1e8f196308 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -3240,8 +3240,14 @@ def enum_members(self) -> list[str]: and name not in EXCLUDED_ENUM_ATTRIBUTES and not name.startswith("__") and sym.node.has_explicit_value - and not (isinstance(typ := mypy.types.get_proper_type(sym.node.type), mypy.types.Instance) and typ.type.fullname == "enum.nonmember") - ) or ( + and not ( + isinstance( + typ := mypy.types.get_proper_type(sym.node.type), mypy.types.Instance + ) + and typ.type.fullname == "enum.nonmember" + ) + ) + or ( isinstance(sym.node, Decorator) and any( dec.fullname == "enum.member" From ab269b83e07c1338ca083c93f7624ad6a4f51843 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 28 Jan 2025 15:35:06 +0300 Subject: [PATCH 3/4] Properly account for `member` and `nonmember` in `TypeInfo.enum_members` --- test-data/unit/check-enum.test | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 3a4fc5fa68fd..118d6a268adf 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -1944,13 +1944,8 @@ class D(C): # E: Cannot extend enum with existing members: "C" \ import lib [file lib.pyi] -from enum import Enum, member, nonmember - -class A(Enum): # E: Detected enum "lib.A" in a type stub with zero members. There is a chance this is due to a recent change in the semantics of enum membership. If so, use `member = value` to mark an enum member, instead of `member: type` \ - # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members - x = nonmember(1) - -class B(Enum): +from enum import Enum, member +class A(Enum): @member def x(self) -> None: ... [builtins fixtures/bool.pyi] From f50f4b3909879bfaca278451e4c5b10dc3162a1f Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 28 Jan 2025 15:35:20 +0300 Subject: [PATCH 4/4] Properly account for `member` and `nonmember` in `TypeInfo.enum_members` --- test-data/unit/check-enum.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 118d6a268adf..961059b70de9 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -1938,7 +1938,7 @@ class D(C): # E: Cannot extend enum with existing members: "C" \ x: int # E: Cannot assign to final name "x" [builtins fixtures/bool.pyi] -[case testEnumNotFinalWithMethodsAndUninitializedValuesStubMemberAndNonMember] +[case testEnumNotFinalWithMethodsAndUninitializedValuesStubMember] # flags: --python-version 3.11 # This was added in 3.11 import lib