Skip to content

Commit dac88f3

Browse files
Support enum.member for python3.11+ (#17382)
There are no tests for `@enum.member` used as a decorator, because I can only decorate classes and functions, which are not supported right now: https://mypy-play.net/?mypy=latest&python=3.12&gist=449ee8c12eba9f807cfc7832f1ea2c49 ```python import enum class A(enum.Enum): class x: ... reveal_type(A.x) # Revealed type is "def () -> __main__.A.x" ``` This issue is separate and rather complex, so I would prefer to solve it independently. Refs #17376 --------- Co-authored-by: Alex Waygood <[email protected]>
1 parent 5dd062a commit dac88f3

File tree

4 files changed

+45
-1
lines changed

4 files changed

+45
-1
lines changed

mypy/plugins/default.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class DefaultPlugin(Plugin):
4141
"""Type checker plugin that is enabled by default."""
4242

4343
def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None:
44-
from mypy.plugins import ctypes, singledispatch
44+
from mypy.plugins import ctypes, enums, singledispatch
4545

4646
if fullname == "_ctypes.Array":
4747
return ctypes.array_constructor_callback
@@ -51,6 +51,8 @@ def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type]
5151
import mypy.plugins.functools
5252

5353
return mypy.plugins.functools.partial_new_callback
54+
elif fullname == "enum.member":
55+
return enums.enum_member_callback
5456

5557
return None
5658

mypy/plugins/enums.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ def _infer_value_type_with_auto_fallback(
8787
return None
8888
proper_type = get_proper_type(fixup_partial_type(proper_type))
8989
if not (isinstance(proper_type, Instance) and proper_type.type.fullname == "enum.auto"):
90+
if is_named_instance(proper_type, "enum.member") and proper_type.args:
91+
return proper_type.args[0]
9092
return proper_type
9193
assert isinstance(ctx.type, Instance), "An incorrect ctx.type was passed."
9294
info = ctx.type.type
@@ -126,6 +128,22 @@ def _implements_new(info: TypeInfo) -> bool:
126128
return type_with_new.fullname not in ("enum.Enum", "enum.IntEnum", "enum.StrEnum")
127129

128130

131+
def enum_member_callback(ctx: mypy.plugin.FunctionContext) -> Type:
132+
"""By default `member(1)` will be infered as `member[int]`,
133+
we want to improve the inference to be `Literal[1]` here."""
134+
if ctx.arg_types or ctx.arg_types[0]:
135+
arg = get_proper_type(ctx.arg_types[0][0])
136+
proper_return = get_proper_type(ctx.default_return_type)
137+
if (
138+
isinstance(arg, Instance)
139+
and arg.last_known_value
140+
and isinstance(proper_return, Instance)
141+
and len(proper_return.args) == 1
142+
):
143+
return proper_return.copy_modified(args=[arg])
144+
return ctx.default_return_type
145+
146+
129147
def enum_value_callback(ctx: mypy.plugin.AttributeContext) -> Type:
130148
"""This plugin refines the 'value' attribute in enums to refer to
131149
the original underlying value. For example, suppose we have the

test-data/unit/check-enum.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2166,3 +2166,22 @@ class Other(Enum):
21662166
reveal_type(Other.a) # N: Revealed type is "Literal[__main__.Other.a]?"
21672167
reveal_type(Other.Support.b) # N: Revealed type is "builtins.int"
21682168
[builtins fixtures/dict.pyi]
2169+
2170+
2171+
[case testEnumMemberSupport]
2172+
# flags: --python-version 3.11
2173+
# This was added in 3.11
2174+
from enum import Enum, member
2175+
2176+
class A(Enum):
2177+
x = member(1)
2178+
y = 2
2179+
2180+
reveal_type(A.x) # N: Revealed type is "Literal[__main__.A.x]?"
2181+
reveal_type(A.x.value) # N: Revealed type is "Literal[1]?"
2182+
reveal_type(A.y) # N: Revealed type is "Literal[__main__.A.y]?"
2183+
reveal_type(A.y.value) # N: Revealed type is "Literal[2]?"
2184+
2185+
def some_a(a: A):
2186+
reveal_type(a.value) # N: Revealed type is "Union[Literal[1]?, Literal[2]?]"
2187+
[builtins fixtures/dict.pyi]

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,8 @@ class StrEnum(str, Enum):
5353
class nonmember(Generic[_T]):
5454
value: _T
5555
def __init__(self, value: _T) -> None: ...
56+
57+
# It is python-3.11+ only:
58+
class member(Generic[_T]):
59+
value: _T
60+
def __init__(self, value: _T) -> None: ...

0 commit comments

Comments
 (0)