Skip to content

Commit 4fb187f

Browse files
authored
Always use .enum_members to find enum members (#18675)
Closes #18565 This fixes the problem with `nonmember` and `member` special cases, however, it required me to change one test case. See `testEnumReachabilityPEP484ExampleSingletonWithMethod` change, because in runtime `token` is not a member by default, at least in recent python versions. Proof: ```python # 3.14 >>> from enum import Enum >>> class Empty(Enum): ... token = lambda x: x ... >>> Empty.token <function Empty.<lambda> at 0x101251250> >>> Empty.token.value ``` and ```python # 3.11 >>> from enum import Enum >>> class Empty(Enum): ... token = lambda x: x ... >>> Empty.token <function Empty.<lambda> at 0x104757600> >>> Empty.token.value Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'function' object has no attribute 'value' ``` So, I had to add `member()` there to make the test pass.
1 parent 12aa642 commit 4fb187f

File tree

3 files changed

+121
-33
lines changed

3 files changed

+121
-33
lines changed

mypy/checker.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -2788,10 +2788,8 @@ def check_enum(self, defn: ClassDef) -> None:
27882788
self.check_enum_new(defn)
27892789

27902790
def check_final_enum(self, defn: ClassDef, base: TypeInfo) -> None:
2791-
for sym in base.names.values():
2792-
if self.is_final_enum_value(sym):
2793-
self.fail(f'Cannot extend enum with existing members: "{base.name}"', defn)
2794-
break
2791+
if base.enum_members:
2792+
self.fail(f'Cannot extend enum with existing members: "{base.name}"', defn)
27952793

27962794
def is_final_enum_value(self, sym: SymbolTableNode) -> bool:
27972795
if isinstance(sym.node, (FuncBase, Decorator)):

mypy/nodes.py

+49-26
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import mypy.strconv
1616
from mypy.options import Options
17-
from mypy.util import is_typeshed_file, short_type
17+
from mypy.util import is_sunder, is_typeshed_file, short_type
1818
from mypy.visitor import ExpressionVisitor, NodeVisitor, StatementVisitor
1919

2020
if TYPE_CHECKING:
@@ -3246,32 +3246,55 @@ def protocol_members(self) -> list[str]:
32463246

32473247
@property
32483248
def enum_members(self) -> list[str]:
3249-
return [
3250-
name
3251-
for name, sym in self.names.items()
3252-
if (
3253-
(
3254-
isinstance(sym.node, Var)
3255-
and name not in EXCLUDED_ENUM_ATTRIBUTES
3256-
and not name.startswith("__")
3257-
and sym.node.has_explicit_value
3258-
and not (
3259-
isinstance(
3260-
typ := mypy.types.get_proper_type(sym.node.type), mypy.types.Instance
3261-
)
3249+
# TODO: cache the results?
3250+
members = []
3251+
for name, sym in self.names.items():
3252+
# Case 1:
3253+
#
3254+
# class MyEnum(Enum):
3255+
# @member
3256+
# def some(self): ...
3257+
if isinstance(sym.node, Decorator):
3258+
if any(
3259+
dec.fullname == "enum.member"
3260+
for dec in sym.node.decorators
3261+
if isinstance(dec, RefExpr)
3262+
):
3263+
members.append(name)
3264+
continue
3265+
# Case 2:
3266+
#
3267+
# class MyEnum(Enum):
3268+
# x = 1
3269+
#
3270+
# Case 3:
3271+
#
3272+
# class MyEnum(Enum):
3273+
# class Other: ...
3274+
elif isinstance(sym.node, (Var, TypeInfo)):
3275+
if (
3276+
# TODO: properly support ignored names from `_ignore_`
3277+
name in EXCLUDED_ENUM_ATTRIBUTES
3278+
or is_sunder(name)
3279+
or name.startswith("__") # dunder and private
3280+
):
3281+
continue # name is excluded
3282+
3283+
if isinstance(sym.node, Var):
3284+
if not sym.node.has_explicit_value:
3285+
continue # unannotated value not a member
3286+
3287+
typ = mypy.types.get_proper_type(sym.node.type)
3288+
if isinstance(
3289+
typ, mypy.types.FunctionLike
3290+
) or ( # explicit `@member` is required
3291+
isinstance(typ, mypy.types.Instance)
32623292
and typ.type.fullname == "enum.nonmember"
3263-
)
3264-
)
3265-
or (
3266-
isinstance(sym.node, Decorator)
3267-
and any(
3268-
dec.fullname == "enum.member"
3269-
for dec in sym.node.decorators
3270-
if isinstance(dec, RefExpr)
3271-
)
3272-
)
3273-
)
3274-
]
3293+
):
3294+
continue # name is not a member
3295+
3296+
members.append(name)
3297+
return members
32753298

32763299
def __getitem__(self, name: str) -> SymbolTableNode:
32773300
n = self.get(name)

test-data/unit/check-enum.test

+70-3
Original file line numberDiff line numberDiff line change
@@ -1197,16 +1197,20 @@ def func(x: Union[int, None, Empty] = _empty) -> int:
11971197
[builtins fixtures/primitives.pyi]
11981198

11991199
[case testEnumReachabilityPEP484ExampleSingletonWithMethod]
1200+
# flags: --python-version 3.11
12001201
from typing import Final, Union
1201-
from enum import Enum
1202+
from enum import Enum, member
12021203

12031204
class Empty(Enum):
1204-
token = lambda x: x
1205+
# note, that without `member` we cannot tell that `token` is a member:
1206+
token = member(lambda x: x)
12051207

12061208
def f(self) -> int:
12071209
return 1
12081210

12091211
_empty = Empty.token
1212+
reveal_type(_empty) # N: Revealed type is "__main__.Empty"
1213+
reveal_type(Empty.f) # N: Revealed type is "def (self: __main__.Empty) -> builtins.int"
12101214

12111215
def func(x: Union[int, None, Empty] = _empty) -> int:
12121216
boom = x + 42 # E: Unsupported left operand type for + ("None") \
@@ -1615,6 +1619,65 @@ class ErrorIntFlagWithoutValue(NonEmptyIntFlag): # E: Cannot extend enum with e
16151619
pass
16161620
[builtins fixtures/bool.pyi]
16171621

1622+
[case testEnumImplicitlyFinalForSubclassingWithCallableMember]
1623+
# flags: --python-version 3.11
1624+
from enum import Enum, IntEnum, Flag, IntFlag, member
1625+
1626+
class NonEmptyEnum(Enum):
1627+
@member
1628+
def call(self) -> None: ...
1629+
class NonEmptyIntEnum(IntEnum):
1630+
@member
1631+
def call(self) -> None: ...
1632+
class NonEmptyFlag(Flag):
1633+
@member
1634+
def call(self) -> None: ...
1635+
class NonEmptyIntFlag(IntFlag):
1636+
@member
1637+
def call(self) -> None: ...
1638+
1639+
class ErrorEnumWithoutValue(NonEmptyEnum): # E: Cannot extend enum with existing members: "NonEmptyEnum"
1640+
pass
1641+
class ErrorIntEnumWithoutValue(NonEmptyIntEnum): # E: Cannot extend enum with existing members: "NonEmptyIntEnum"
1642+
pass
1643+
class ErrorFlagWithoutValue(NonEmptyFlag): # E: Cannot extend enum with existing members: "NonEmptyFlag"
1644+
pass
1645+
class ErrorIntFlagWithoutValue(NonEmptyIntFlag): # E: Cannot extend enum with existing members: "NonEmptyIntFlag"
1646+
pass
1647+
[builtins fixtures/bool.pyi]
1648+
1649+
[case testEnumCanExtendEnumsWithNonMembers]
1650+
# flags: --python-version 3.11
1651+
from enum import Enum, IntEnum, Flag, IntFlag, nonmember
1652+
1653+
class NonEmptyEnum(Enum):
1654+
x = nonmember(1)
1655+
class NonEmptyIntEnum(IntEnum):
1656+
x = nonmember(1)
1657+
class NonEmptyFlag(Flag):
1658+
x = nonmember(1)
1659+
class NonEmptyIntFlag(IntFlag):
1660+
x = nonmember(1)
1661+
1662+
class ErrorEnumWithoutValue(NonEmptyEnum):
1663+
pass
1664+
class ErrorIntEnumWithoutValue(NonEmptyIntEnum):
1665+
pass
1666+
class ErrorFlagWithoutValue(NonEmptyFlag):
1667+
pass
1668+
class ErrorIntFlagWithoutValue(NonEmptyIntFlag):
1669+
pass
1670+
[builtins fixtures/bool.pyi]
1671+
1672+
[case testLambdaIsNotEnumMember]
1673+
from enum import Enum
1674+
1675+
class My(Enum):
1676+
x = lambda a: a
1677+
1678+
class Other(My): ...
1679+
[builtins fixtures/bool.pyi]
1680+
16181681
[case testSubclassingNonFinalEnums]
16191682
from enum import Enum, IntEnum, Flag, IntFlag, EnumMeta
16201683

@@ -1839,6 +1902,10 @@ from enum import Enum
18391902
class A(Enum):
18401903
class Inner: pass
18411904
class B(A): pass # E: Cannot extend enum with existing members: "A"
1905+
1906+
class A1(Enum):
1907+
class __Inner: pass
1908+
class B1(A1): pass
18421909
[builtins fixtures/bool.pyi]
18431910

18441911
[case testEnumFinalSpecialProps]
@@ -1922,7 +1989,7 @@ from enum import Enum
19221989
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` \
19231990
# N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members
19241991
x: int
1925-
class B(A): # E: Cannot extend enum with existing members: "A"
1992+
class B(A):
19261993
x = 1 # E: Cannot override writable attribute "x" with a final one
19271994

19281995
class C(Enum):

0 commit comments

Comments
 (0)