Skip to content

Commit 5d59e43

Browse files
authored
Support _value_ as a fallback for ellipsis Enum members (#19352)
Fixes #19334. This does not affect enums with explicit values different from ellipsis and is limited to enums defined in stub files.
1 parent 40277a1 commit 5d59e43

File tree

5 files changed

+139
-16
lines changed

5 files changed

+139
-16
lines changed

mypy/plugins/enums.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
from typing import TypeVar, cast
1818

1919
import mypy.plugin # To avoid circular imports.
20-
from mypy.nodes import TypeInfo
20+
from mypy.checker import TypeChecker
21+
from mypy.nodes import TypeInfo, Var
2122
from mypy.subtypes import is_equivalent
2223
from mypy.typeops import fixup_partial_type, make_simplified_union
2324
from mypy.types import (
25+
ELLIPSIS_TYPE_NAMES,
2426
CallableType,
2527
Instance,
2628
LiteralType,
@@ -79,6 +81,19 @@ def _infer_value_type_with_auto_fallback(
7981
if proper_type is None:
8082
return None
8183
proper_type = get_proper_type(fixup_partial_type(proper_type))
84+
# Enums in stubs may have ... instead of actual values. If `_value_` is annotated
85+
# (manually or inherited from IntEnum, for example), it is a more reasonable guess
86+
# than literal ellipsis type.
87+
if (
88+
_is_defined_in_stub(ctx)
89+
and isinstance(proper_type, Instance)
90+
and proper_type.type.fullname in ELLIPSIS_TYPE_NAMES
91+
and isinstance(ctx.type, Instance)
92+
):
93+
value_type = ctx.type.type.get("_value_")
94+
if value_type is not None and isinstance(var := value_type.node, Var):
95+
return var.type
96+
return proper_type
8297
if not (isinstance(proper_type, Instance) and proper_type.type.fullname == "enum.auto"):
8398
if is_named_instance(proper_type, "enum.member") and proper_type.args:
8499
return proper_type.args[0]
@@ -106,6 +121,11 @@ def _infer_value_type_with_auto_fallback(
106121
return ctx.default_attr_type
107122

108123

124+
def _is_defined_in_stub(ctx: mypy.plugin.AttributeContext) -> bool:
125+
assert isinstance(ctx.api, TypeChecker)
126+
return isinstance(ctx.type, Instance) and ctx.api.modules[ctx.type.type.module_name].is_stub
127+
128+
109129
def _implements_new(info: TypeInfo) -> bool:
110130
"""Check whether __new__ comes from enum.Enum or was implemented in a
111131
subclass. In the latter case, we must infer Any as long as mypy can't infer

mypy/stubtest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1149,7 +1149,7 @@ def verify_var(
11491149
proper_type = mypy.types.get_proper_type(stub.type)
11501150
if (
11511151
isinstance(proper_type, mypy.types.Instance)
1152-
and proper_type.type.fullname == "builtins.ellipsis"
1152+
and proper_type.type.fullname in mypy.types.ELLIPSIS_TYPE_NAMES
11531153
):
11541154
should_error = False
11551155

mypy/types.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@
181181
# Supported @override decorator names.
182182
OVERRIDE_DECORATOR_NAMES: Final = ("typing.override", "typing_extensions.override")
183183

184+
ELLIPSIS_TYPE_NAMES: Final = ("builtins.ellipsis", "types.EllipsisType")
185+
184186
# A placeholder used for Bogus[...] parameters
185187
_dummy: Final[Any] = object()
186188

@@ -1574,7 +1576,7 @@ def is_singleton_type(self) -> bool:
15741576
return (
15751577
self.type.is_enum
15761578
and len(self.type.enum_members) == 1
1577-
or self.type.fullname in {"builtins.ellipsis", "types.EllipsisType"}
1579+
or self.type.fullname in ELLIPSIS_TYPE_NAMES
15781580
)
15791581

15801582

test-data/unit/check-enum.test

Lines changed: 109 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -618,9 +618,8 @@ reveal_type(B.a) # N: Revealed type is "Literal[__main__.B.a]?"
618618
reveal_type(A.x.name) # N: Revealed type is "Literal['x']?"
619619
reveal_type(B.a.name) # N: Revealed type is "Literal['a']?"
620620

621-
# TODO: The revealed type should be 'int' here
622-
reveal_type(A.x.value) # N: Revealed type is "Any"
623-
reveal_type(B.a.value) # N: Revealed type is "Any"
621+
reveal_type(A.x.value) # N: Revealed type is "builtins.int"
622+
reveal_type(B.a.value) # N: Revealed type is "builtins.int"
624623
[builtins fixtures/enum.pyi]
625624

626625
[case testAnonymousFunctionalEnum]
@@ -755,12 +754,10 @@ class B2(IntEnum):
755754
class B3(IntEnum):
756755
x = 1
757756

758-
# TODO: getting B1.x._value_ and B2.x._value_ to have type 'int' requires a typeshed change
759-
760757
is_x(reveal_type(B1.x.name)) # N: Revealed type is "Literal['x']"
761758
is_x(reveal_type(B1.x._name_)) # N: Revealed type is "Literal['x']"
762759
reveal_type(B1.x.value) # N: Revealed type is "builtins.int"
763-
reveal_type(B1.x._value_) # N: Revealed type is "Any"
760+
reveal_type(B1.x._value_) # N: Revealed type is "builtins.int"
764761
is_x(reveal_type(B2.x.name)) # N: Revealed type is "Literal['x']"
765762
is_x(reveal_type(B2.x._name_)) # N: Revealed type is "Literal['x']"
766763
reveal_type(B2.x.value) # N: Revealed type is "builtins.int"
@@ -770,9 +767,6 @@ is_x(reveal_type(B3.x._name_)) # N: Revealed type is "Literal['x']"
770767
reveal_type(B3.x.value) # N: Revealed type is "Literal[1]?"
771768
reveal_type(B3.x._value_) # N: Revealed type is "Literal[1]?"
772769

773-
# TODO: C1.x.value and C2.x.value should also be of type 'int'
774-
# This requires either a typeshed change or a plugin refinement
775-
776770
C1 = IntFlag('C1', 'x')
777771
class C2(IntFlag):
778772
x = auto()
@@ -781,8 +775,8 @@ class C3(IntFlag):
781775

782776
is_x(reveal_type(C1.x.name)) # N: Revealed type is "Literal['x']"
783777
is_x(reveal_type(C1.x._name_)) # N: Revealed type is "Literal['x']"
784-
reveal_type(C1.x.value) # N: Revealed type is "Any"
785-
reveal_type(C1.x._value_) # N: Revealed type is "Any"
778+
reveal_type(C1.x.value) # N: Revealed type is "builtins.int"
779+
reveal_type(C1.x._value_) # N: Revealed type is "builtins.int"
786780
is_x(reveal_type(C2.x.name)) # N: Revealed type is "Literal['x']"
787781
is_x(reveal_type(C2.x._name_)) # N: Revealed type is "Literal['x']"
788782
reveal_type(C2.x.value) # N: Revealed type is "builtins.int"
@@ -800,8 +794,8 @@ class D3(Flag):
800794

801795
is_x(reveal_type(D1.x.name)) # N: Revealed type is "Literal['x']"
802796
is_x(reveal_type(D1.x._name_)) # N: Revealed type is "Literal['x']"
803-
reveal_type(D1.x.value) # N: Revealed type is "Any"
804-
reveal_type(D1.x._value_) # N: Revealed type is "Any"
797+
reveal_type(D1.x.value) # N: Revealed type is "builtins.int"
798+
reveal_type(D1.x._value_) # N: Revealed type is "builtins.int"
805799
is_x(reveal_type(D2.x.name)) # N: Revealed type is "Literal['x']"
806800
is_x(reveal_type(D2.x._name_)) # N: Revealed type is "Literal['x']"
807801
reveal_type(D2.x.value) # N: Revealed type is "builtins.int"
@@ -2539,3 +2533,105 @@ def check(thing: Things) -> None:
25392533
return None
25402534
return None # E: Statement is unreachable
25412535
[builtins fixtures/enum.pyi]
2536+
2537+
[case testSunderValueTypeEllipsis]
2538+
from foo.bar import (
2539+
Basic, FromStub, InheritedInt, InheritedStr, InheritedFlag,
2540+
InheritedIntFlag, Wrapper
2541+
)
2542+
2543+
reveal_type(Basic.FOO) # N: Revealed type is "Literal[foo.bar.Basic.FOO]?"
2544+
reveal_type(Basic.FOO.value) # N: Revealed type is "Literal[1]?"
2545+
reveal_type(Basic.FOO._value_) # N: Revealed type is "builtins.int"
2546+
2547+
reveal_type(FromStub.FOO) # N: Revealed type is "Literal[foo.bar.FromStub.FOO]?"
2548+
reveal_type(FromStub.FOO.value) # N: Revealed type is "builtins.int"
2549+
reveal_type(FromStub.FOO._value_) # N: Revealed type is "builtins.int"
2550+
2551+
reveal_type(Wrapper.Nested.FOO) # N: Revealed type is "Literal[foo.bar.Wrapper.Nested.FOO]?"
2552+
reveal_type(Wrapper.Nested.FOO.value) # N: Revealed type is "builtins.int"
2553+
reveal_type(Wrapper.Nested.FOO._value_) # N: Revealed type is "builtins.int"
2554+
2555+
reveal_type(InheritedInt.FOO) # N: Revealed type is "Literal[foo.bar.InheritedInt.FOO]?"
2556+
reveal_type(InheritedInt.FOO.value) # N: Revealed type is "builtins.int"
2557+
reveal_type(InheritedInt.FOO._value_) # N: Revealed type is "builtins.int"
2558+
2559+
reveal_type(InheritedStr.FOO) # N: Revealed type is "Literal[foo.bar.InheritedStr.FOO]?"
2560+
reveal_type(InheritedStr.FOO.value) # N: Revealed type is "builtins.str"
2561+
reveal_type(InheritedStr.FOO._value_) # N: Revealed type is "builtins.str"
2562+
2563+
reveal_type(InheritedFlag.FOO) # N: Revealed type is "Literal[foo.bar.InheritedFlag.FOO]?"
2564+
reveal_type(InheritedFlag.FOO.value) # N: Revealed type is "builtins.int"
2565+
reveal_type(InheritedFlag.FOO._value_) # N: Revealed type is "builtins.int"
2566+
2567+
reveal_type(InheritedIntFlag.FOO) # N: Revealed type is "Literal[foo.bar.InheritedIntFlag.FOO]?"
2568+
reveal_type(InheritedIntFlag.FOO.value) # N: Revealed type is "builtins.int"
2569+
reveal_type(InheritedIntFlag.FOO._value_) # N: Revealed type is "builtins.int"
2570+
2571+
[file foo/__init__.pyi]
2572+
[file foo/bar/__init__.pyi]
2573+
from enum import Enum, IntEnum, StrEnum, Flag, IntFlag
2574+
2575+
class Basic(Enum):
2576+
_value_: int
2577+
FOO = 1
2578+
2579+
class FromStub(Enum):
2580+
_value_: int
2581+
FOO = ...
2582+
2583+
class Wrapper:
2584+
class Nested(Enum):
2585+
_value_: int
2586+
FOO = ...
2587+
2588+
class InheritedInt(IntEnum):
2589+
FOO = ...
2590+
2591+
class InheritedStr(StrEnum):
2592+
FOO = ...
2593+
2594+
class InheritedFlag(Flag):
2595+
FOO = ...
2596+
2597+
class InheritedIntFlag(IntFlag):
2598+
FOO = ...
2599+
[builtins fixtures/enum.pyi]
2600+
2601+
[case testSunderValueTypeEllipsisNonStub]
2602+
from enum import Enum, StrEnum
2603+
2604+
class Basic(Enum):
2605+
_value_: int
2606+
FOO = 1
2607+
2608+
reveal_type(Basic.FOO) # N: Revealed type is "Literal[__main__.Basic.FOO]?"
2609+
reveal_type(Basic.FOO.value) # N: Revealed type is "Literal[1]?"
2610+
reveal_type(Basic.FOO._value_) # N: Revealed type is "builtins.int"
2611+
2612+
# TODO: this and below should produce diagnostics, Ellipsis is not assignable to int
2613+
# Now we do not check members against _value_ at all.
2614+
2615+
class FromStub(Enum):
2616+
_value_: int
2617+
FOO = ...
2618+
2619+
reveal_type(FromStub.FOO) # N: Revealed type is "Literal[__main__.FromStub.FOO]?"
2620+
reveal_type(FromStub.FOO.value) # N: Revealed type is "builtins.ellipsis"
2621+
reveal_type(FromStub.FOO._value_) # N: Revealed type is "builtins.int"
2622+
2623+
class InheritedStr(StrEnum):
2624+
FOO = ...
2625+
2626+
reveal_type(InheritedStr.FOO) # N: Revealed type is "Literal[__main__.InheritedStr.FOO]?"
2627+
reveal_type(InheritedStr.FOO.value) # N: Revealed type is "builtins.ellipsis"
2628+
reveal_type(InheritedStr.FOO._value_) # N: Revealed type is "builtins.ellipsis"
2629+
2630+
class Wrapper:
2631+
class Nested(StrEnum):
2632+
FOO = ...
2633+
2634+
reveal_type(Wrapper.Nested.FOO) # N: Revealed type is "Literal[__main__.Wrapper.Nested.FOO]?"
2635+
reveal_type(Wrapper.Nested.FOO.value) # N: Revealed type is "builtins.ellipsis"
2636+
reveal_type(Wrapper.Nested.FOO._value_) # N: Revealed type is "builtins.ellipsis"
2637+
[builtins fixtures/enum.pyi]

test-data/unit/lib-stub/enum.pyi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,16 @@ class Enum(metaclass=EnumMeta):
2929

3030
class IntEnum(int, Enum):
3131
value: int
32+
_value_: int
3233
def __new__(cls: Type[_T], value: Union[int, _T]) -> _T: ...
3334

3435
def unique(enumeration: _T) -> _T: pass
3536

3637
# In reality Flag and IntFlag are 3.6 only
3738

3839
class Flag(Enum):
40+
value: int
41+
_value_: int
3942
def __or__(self: _T, other: Union[int, _T]) -> _T: pass
4043

4144

@@ -49,6 +52,8 @@ class auto(IntFlag):
4952

5053
# It is python-3.11+ only:
5154
class StrEnum(str, Enum):
55+
_value_: str
56+
value: str
5257
def __new__(cls: Type[_T], value: str | _T) -> _T: ...
5358

5459
# It is python-3.11+ only:

0 commit comments

Comments
 (0)