Skip to content

Commit af10964

Browse files
authored
Fix enum value inference with user-defined data type mixin (#16320)
Fixes enum value inference in cases where the value type is of a user-defined data type class (with `__new__`). According to https://docs.python.org/3/howto/enum.html#others when defining an `Enum` subclass with a custom data type: > A data type is a mixin that defines `__new__()`, or a dataclass This fixes a regression introduced by #10057 to fix #10000. The `not ti.fullname.startswith("builtins.")` clause seemed to be intended to catch enums with a built-in data type like int or bytes, but this is overly broad. It should allow any type so long as it is not itself an enum.Enum subclass.
1 parent a62a906 commit af10964

2 files changed

Lines changed: 21 additions & 6 deletions

File tree

mypy/plugins/enums.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,14 @@ def _is_defined_in_stub(ctx: mypy.plugin.AttributeContext) -> bool:
128128

129129
def _implements_new(info: TypeInfo) -> bool:
130130
"""Check whether __new__ comes from enum.Enum or was implemented in a
131-
subclass. In the latter case, we must infer Any as long as mypy can't infer
131+
subclass of enum.Enum. In the latter case, we must infer Any as long as mypy can't infer
132132
the type of _value_ from assignments in __new__.
133+
134+
If, however, __new__ comes from a user-defined class that is not an Enum subclass (i.e.
135+
the data type) this is allowed, because we should in general infer that an enum entry's
136+
value has that type.
133137
"""
134-
type_with_new = _first(
135-
ti
136-
for ti in info.mro
137-
if ti.names.get("__new__") and not ti.fullname.startswith("builtins.")
138-
)
138+
type_with_new = _first(ti for ti in info.mro if ti.is_enum and ti.names.get("__new__"))
139139
if type_with_new is None:
140140
return False
141141
return type_with_new.fullname not in ("enum.Enum", "enum.IntEnum", "enum.StrEnum")

test-data/unit/check-enum.test

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,6 +1524,21 @@ reveal_type(a._value_) # N: Revealed type is "Any"
15241524
[builtins fixtures/primitives.pyi]
15251525
[typing fixtures/typing-medium.pyi]
15261526

1527+
[case testValueTypeWithUserDataType]
1528+
from enum import Enum
1529+
from typing import Any
1530+
1531+
class Data:
1532+
def __new__(cls, value: Any) -> Data: pass
1533+
1534+
class DataEnum(Data, Enum):
1535+
A = Data(1)
1536+
1537+
reveal_type(DataEnum.A) # N: Revealed type is "Literal[__main__.DataEnum.A]?"
1538+
reveal_type(DataEnum.A.value) # N: Revealed type is "__main__.Data"
1539+
reveal_type(DataEnum.A._value_) # N: Revealed type is "__main__.Data"
1540+
[builtins fixtures/tuple.pyi]
1541+
15271542
[case testEnumNarrowedToTwoLiterals]
15281543
# Regression test: two literals of an enum would be joined
15291544
# as the full type, regardless of the amount of elements

0 commit comments

Comments
 (0)