From 96662768de883aa23ddbccdbf3d07a68ba81c59c Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sat, 28 Jun 2025 00:53:09 +0200 Subject: [PATCH 1/8] Use _value_ as a fallback for ellipsis Enum members --- mypy/plugins/enums.py | 16 +++++++++- test-data/unit/check-enum.test | 52 ++++++++++++++++++++++++++++++-- test-data/unit/lib-stub/enum.pyi | 5 +++ 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/mypy/plugins/enums.py b/mypy/plugins/enums.py index 8b7c5df6f51f..3e09f0b81bfa 100644 --- a/mypy/plugins/enums.py +++ b/mypy/plugins/enums.py @@ -17,7 +17,7 @@ from typing import Final, TypeVar, cast import mypy.plugin # To avoid circular imports. -from mypy.nodes import TypeInfo +from mypy.nodes import TypeInfo, Var from mypy.semanal_enum import ENUM_BASES from mypy.subtypes import is_equivalent from mypy.typeops import fixup_partial_type, make_simplified_union @@ -87,6 +87,20 @@ def _infer_value_type_with_auto_fallback( if proper_type is None: return None proper_type = get_proper_type(fixup_partial_type(proper_type)) + # Enums in stubs may have ... instead of actual values. If `_value_` is annotated + # (manually or inherited from IntEnum, for example), it is a more reasonable guess + # than literal ellipsis type. + if isinstance(proper_type, Instance) and proper_type.type.fullname in { + "types.EllipsisType", + "builtins.ellipsis", + }: + if ( + isinstance(ctx.type, Instance) + and (value_type := ctx.type.type.get("_value_")) + and isinstance(var := value_type.node, Var) + ): + return var.type + return proper_type if not (isinstance(proper_type, Instance) and proper_type.type.fullname == "enum.auto"): if is_named_instance(proper_type, "enum.member") and proper_type.args: return proper_type.args[0] diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 1ab8109eda75..85b82188b016 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -755,12 +755,10 @@ class B2(IntEnum): class B3(IntEnum): x = 1 -# TODO: getting B1.x._value_ and B2.x._value_ to have type 'int' requires a typeshed change - is_x(reveal_type(B1.x.name)) # N: Revealed type is "Literal['x']" is_x(reveal_type(B1.x._name_)) # N: Revealed type is "Literal['x']" reveal_type(B1.x.value) # N: Revealed type is "builtins.int" -reveal_type(B1.x._value_) # N: Revealed type is "Any" +reveal_type(B1.x._value_) # N: Revealed type is "builtins.int" is_x(reveal_type(B2.x.name)) # N: Revealed type is "Literal['x']" is_x(reveal_type(B2.x._name_)) # N: Revealed type is "Literal['x']" reveal_type(B2.x.value) # N: Revealed type is "builtins.int" @@ -2539,3 +2537,51 @@ def check(thing: Things) -> None: return None return None # E: Statement is unreachable [builtins fixtures/enum.pyi] + +[case testSunderValueType] +from enum import Enum, IntEnum, StrEnum, Flag, IntFlag + +class Basic(Enum): + _value_: int + FOO = 1 + +reveal_type(Basic.FOO) # N: Revealed type is "Literal[__main__.Basic.FOO]?" +reveal_type(Basic.FOO.value) # N: Revealed type is "Literal[1]?" +reveal_type(Basic.FOO._value_) # N: Revealed type is "builtins.int" + +class FromStub(Enum): + _value_: int + FOO = ... + +reveal_type(FromStub.FOO) # N: Revealed type is "Literal[__main__.FromStub.FOO]?" +reveal_type(FromStub.FOO.value) # N: Revealed type is "builtins.int" +reveal_type(FromStub.FOO._value_) # N: Revealed type is "builtins.int" + +class InheritedInt(IntEnum): + FOO = ... + +reveal_type(InheritedInt.FOO) # N: Revealed type is "Literal[__main__.InheritedInt.FOO]?" +reveal_type(InheritedInt.FOO.value) # N: Revealed type is "builtins.int" +reveal_type(InheritedInt.FOO._value_) # N: Revealed type is "builtins.int" + +class InheritedStr(StrEnum): + FOO = ... + +reveal_type(InheritedStr.FOO) # N: Revealed type is "Literal[__main__.InheritedStr.FOO]?" +reveal_type(InheritedStr.FOO.value) # N: Revealed type is "builtins.str" +reveal_type(InheritedStr.FOO._value_) # N: Revealed type is "builtins.str" + +class InheritedFlag(Flag): + FOO = ... + +reveal_type(InheritedFlag.FOO) # N: Revealed type is "Literal[__main__.InheritedFlag.FOO]?" +reveal_type(InheritedFlag.FOO.value) # N: Revealed type is "builtins.int" +reveal_type(InheritedFlag.FOO._value_) # N: Revealed type is "builtins.int" + +class InheritedIntFlag(IntFlag): + FOO = ... + +reveal_type(InheritedIntFlag.FOO) # N: Revealed type is "Literal[__main__.InheritedIntFlag.FOO]?" +reveal_type(InheritedIntFlag.FOO.value) # N: Revealed type is "builtins.int" +reveal_type(InheritedIntFlag.FOO._value_) # N: Revealed type is "builtins.int" +[builtins fixtures/enum.pyi] diff --git a/test-data/unit/lib-stub/enum.pyi b/test-data/unit/lib-stub/enum.pyi index ccb3818b9d25..5047f7083804 100644 --- a/test-data/unit/lib-stub/enum.pyi +++ b/test-data/unit/lib-stub/enum.pyi @@ -29,6 +29,7 @@ class Enum(metaclass=EnumMeta): class IntEnum(int, Enum): value: int + _value_: int def __new__(cls: Type[_T], value: Union[int, _T]) -> _T: ... def unique(enumeration: _T) -> _T: pass @@ -36,6 +37,8 @@ def unique(enumeration: _T) -> _T: pass # In reality Flag and IntFlag are 3.6 only class Flag(Enum): + value: int + _value_: int def __or__(self: _T, other: Union[int, _T]) -> _T: pass @@ -49,6 +52,8 @@ class auto(IntFlag): # It is python-3.11+ only: class StrEnum(str, Enum): + _value_: str + value: str def __new__(cls: Type[_T], value: str | _T) -> _T: ... # It is python-3.11+ only: From 80c82b741a46772d224636d2583a0b148e9b82e4 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sat, 28 Jun 2025 02:52:10 +0200 Subject: [PATCH 2/8] Sync failing tests --- test-data/unit/check-enum.test | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 85b82188b016..8438672710d5 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -619,8 +619,8 @@ reveal_type(A.x.name) # N: Revealed type is "Literal['x']?" reveal_type(B.a.name) # N: Revealed type is "Literal['a']?" # TODO: The revealed type should be 'int' here -reveal_type(A.x.value) # N: Revealed type is "Any" -reveal_type(B.a.value) # N: Revealed type is "Any" +reveal_type(A.x.value) # N: Revealed type is "builtins.int" +reveal_type(B.a.value) # N: Revealed type is "builtins.int" [builtins fixtures/enum.pyi] [case testAnonymousFunctionalEnum] @@ -779,8 +779,8 @@ class C3(IntFlag): is_x(reveal_type(C1.x.name)) # N: Revealed type is "Literal['x']" is_x(reveal_type(C1.x._name_)) # N: Revealed type is "Literal['x']" -reveal_type(C1.x.value) # N: Revealed type is "Any" -reveal_type(C1.x._value_) # N: Revealed type is "Any" +reveal_type(C1.x.value) # N: Revealed type is "builtins.int" +reveal_type(C1.x._value_) # N: Revealed type is "builtins.int" is_x(reveal_type(C2.x.name)) # N: Revealed type is "Literal['x']" is_x(reveal_type(C2.x._name_)) # N: Revealed type is "Literal['x']" reveal_type(C2.x.value) # N: Revealed type is "builtins.int" @@ -798,8 +798,8 @@ class D3(Flag): is_x(reveal_type(D1.x.name)) # N: Revealed type is "Literal['x']" is_x(reveal_type(D1.x._name_)) # N: Revealed type is "Literal['x']" -reveal_type(D1.x.value) # N: Revealed type is "Any" -reveal_type(D1.x._value_) # N: Revealed type is "Any" +reveal_type(D1.x.value) # N: Revealed type is "builtins.int" +reveal_type(D1.x._value_) # N: Revealed type is "builtins.int" is_x(reveal_type(D2.x.name)) # N: Revealed type is "Literal['x']" is_x(reveal_type(D2.x._name_)) # N: Revealed type is "Literal['x']" reveal_type(D2.x.value) # N: Revealed type is "builtins.int" From 7fd34cec07a9b181d45034777b1885accf6cafa2 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sat, 28 Jun 2025 21:52:29 +0200 Subject: [PATCH 3/8] Reuse ellipsis type names --- mypy/plugins/enums.py | 6 ++---- mypy/stubtest.py | 2 +- mypy/types.py | 4 +++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mypy/plugins/enums.py b/mypy/plugins/enums.py index 3e09f0b81bfa..929b8d80e023 100644 --- a/mypy/plugins/enums.py +++ b/mypy/plugins/enums.py @@ -22,6 +22,7 @@ from mypy.subtypes import is_equivalent from mypy.typeops import fixup_partial_type, make_simplified_union from mypy.types import ( + ELLIPSIS_TYPE_NAMES, CallableType, Instance, LiteralType, @@ -90,10 +91,7 @@ def _infer_value_type_with_auto_fallback( # Enums in stubs may have ... instead of actual values. If `_value_` is annotated # (manually or inherited from IntEnum, for example), it is a more reasonable guess # than literal ellipsis type. - if isinstance(proper_type, Instance) and proper_type.type.fullname in { - "types.EllipsisType", - "builtins.ellipsis", - }: + if isinstance(proper_type, Instance) and proper_type.type.fullname in ELLIPSIS_TYPE_NAMES: if ( isinstance(ctx.type, Instance) and (value_type := ctx.type.type.get("_value_")) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 8ea9d786be22..d16e491fb1ab 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1149,7 +1149,7 @@ def verify_var( proper_type = mypy.types.get_proper_type(stub.type) if ( isinstance(proper_type, mypy.types.Instance) - and proper_type.type.fullname == "builtins.ellipsis" + and proper_type.type.fullname in mypy.types.ELLIPSIS_TYPE_NAMES ): should_error = False diff --git a/mypy/types.py b/mypy/types.py index 8ecd2ccf52d9..05b02acc68c0 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -181,6 +181,8 @@ # Supported @override decorator names. OVERRIDE_DECORATOR_NAMES: Final = ("typing.override", "typing_extensions.override") +ELLIPSIS_TYPE_NAMES: Final = ("builtins.ellipsis", "types.EllipsisType") + # A placeholder used for Bogus[...] parameters _dummy: Final[Any] = object() @@ -1574,7 +1576,7 @@ def is_singleton_type(self) -> bool: return ( self.type.is_enum and len(self.type.enum_members) == 1 - or self.type.fullname in {"builtins.ellipsis", "types.EllipsisType"} + or self.type.fullname in ELLIPSIS_TYPE_NAMES ) From b333645b10f0dcdcce268c2568a2f31954454435 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sun, 29 Jun 2025 15:56:21 +0200 Subject: [PATCH 4/8] Remove outdated test comments --- test-data/unit/check-enum.test | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 8438672710d5..baeb45c7c35a 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -618,7 +618,6 @@ reveal_type(B.a) # N: Revealed type is "Literal[__main__.B.a]?" reveal_type(A.x.name) # N: Revealed type is "Literal['x']?" reveal_type(B.a.name) # N: Revealed type is "Literal['a']?" -# TODO: The revealed type should be 'int' here reveal_type(A.x.value) # N: Revealed type is "builtins.int" reveal_type(B.a.value) # N: Revealed type is "builtins.int" [builtins fixtures/enum.pyi] @@ -768,9 +767,6 @@ is_x(reveal_type(B3.x._name_)) # N: Revealed type is "Literal['x']" reveal_type(B3.x.value) # N: Revealed type is "Literal[1]?" reveal_type(B3.x._value_) # N: Revealed type is "Literal[1]?" -# TODO: C1.x.value and C2.x.value should also be of type 'int' -# This requires either a typeshed change or a plugin refinement - C1 = IntFlag('C1', 'x') class C2(IntFlag): x = auto() From 54e296ba86f25654cf16dab1c98210c28b903dca Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sun, 29 Jun 2025 16:12:11 +0200 Subject: [PATCH 5/8] Restrict ellipsis replacement to stubs --- mypy/plugins/enums.py | 7 ++- test-data/unit/check-enum.test | 84 ++++++++++++++++++++++++---------- 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/mypy/plugins/enums.py b/mypy/plugins/enums.py index 929b8d80e023..6819061d3196 100644 --- a/mypy/plugins/enums.py +++ b/mypy/plugins/enums.py @@ -91,7 +91,12 @@ def _infer_value_type_with_auto_fallback( # Enums in stubs may have ... instead of actual values. If `_value_` is annotated # (manually or inherited from IntEnum, for example), it is a more reasonable guess # than literal ellipsis type. - if isinstance(proper_type, Instance) and proper_type.type.fullname in ELLIPSIS_TYPE_NAMES: + source_module = ctx.api.modules[ctx.type.type.fullname.rsplit(".", 1)[0]] + if ( + source_module.is_stub + and isinstance(proper_type, Instance) + and proper_type.type.fullname in ELLIPSIS_TYPE_NAMES + ): if ( isinstance(ctx.type, Instance) and (value_type := ctx.type.type.get("_value_")) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index baeb45c7c35a..ae27d1bafa35 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2534,50 +2534,86 @@ def check(thing: Things) -> None: return None # E: Statement is unreachable [builtins fixtures/enum.pyi] -[case testSunderValueType] +[case testSunderValueTypeEllipsis] +from foo.bar import ( + Basic, FromStub, InheritedInt, InheritedStr, InheritedFlag, InheritedIntFlag +) + +reveal_type(Basic.FOO) # N: Revealed type is "Literal[foo.bar.Basic.FOO]?" +reveal_type(Basic.FOO.value) # N: Revealed type is "Literal[1]?" +reveal_type(Basic.FOO._value_) # N: Revealed type is "builtins.int" + +reveal_type(FromStub.FOO) # N: Revealed type is "Literal[foo.bar.FromStub.FOO]?" +reveal_type(FromStub.FOO.value) # N: Revealed type is "builtins.int" +reveal_type(FromStub.FOO._value_) # N: Revealed type is "builtins.int" + +reveal_type(InheritedInt.FOO) # N: Revealed type is "Literal[foo.bar.InheritedInt.FOO]?" +reveal_type(InheritedInt.FOO.value) # N: Revealed type is "builtins.int" +reveal_type(InheritedInt.FOO._value_) # N: Revealed type is "builtins.int" + +reveal_type(InheritedStr.FOO) # N: Revealed type is "Literal[foo.bar.InheritedStr.FOO]?" +reveal_type(InheritedStr.FOO.value) # N: Revealed type is "builtins.str" +reveal_type(InheritedStr.FOO._value_) # N: Revealed type is "builtins.str" + +reveal_type(InheritedFlag.FOO) # N: Revealed type is "Literal[foo.bar.InheritedFlag.FOO]?" +reveal_type(InheritedFlag.FOO.value) # N: Revealed type is "builtins.int" +reveal_type(InheritedFlag.FOO._value_) # N: Revealed type is "builtins.int" + +reveal_type(InheritedIntFlag.FOO) # N: Revealed type is "Literal[foo.bar.InheritedIntFlag.FOO]?" +reveal_type(InheritedIntFlag.FOO.value) # N: Revealed type is "builtins.int" +reveal_type(InheritedIntFlag.FOO._value_) # N: Revealed type is "builtins.int" + +[file foo/__init__.pyi] +[file foo/bar/__init__.pyi] from enum import Enum, IntEnum, StrEnum, Flag, IntFlag class Basic(Enum): _value_: int FOO = 1 -reveal_type(Basic.FOO) # N: Revealed type is "Literal[__main__.Basic.FOO]?" -reveal_type(Basic.FOO.value) # N: Revealed type is "Literal[1]?" -reveal_type(Basic.FOO._value_) # N: Revealed type is "builtins.int" - class FromStub(Enum): _value_: int FOO = ... -reveal_type(FromStub.FOO) # N: Revealed type is "Literal[__main__.FromStub.FOO]?" -reveal_type(FromStub.FOO.value) # N: Revealed type is "builtins.int" -reveal_type(FromStub.FOO._value_) # N: Revealed type is "builtins.int" - class InheritedInt(IntEnum): FOO = ... -reveal_type(InheritedInt.FOO) # N: Revealed type is "Literal[__main__.InheritedInt.FOO]?" -reveal_type(InheritedInt.FOO.value) # N: Revealed type is "builtins.int" -reveal_type(InheritedInt.FOO._value_) # N: Revealed type is "builtins.int" - class InheritedStr(StrEnum): FOO = ... -reveal_type(InheritedStr.FOO) # N: Revealed type is "Literal[__main__.InheritedStr.FOO]?" -reveal_type(InheritedStr.FOO.value) # N: Revealed type is "builtins.str" -reveal_type(InheritedStr.FOO._value_) # N: Revealed type is "builtins.str" - class InheritedFlag(Flag): FOO = ... -reveal_type(InheritedFlag.FOO) # N: Revealed type is "Literal[__main__.InheritedFlag.FOO]?" -reveal_type(InheritedFlag.FOO.value) # N: Revealed type is "builtins.int" -reveal_type(InheritedFlag.FOO._value_) # N: Revealed type is "builtins.int" - class InheritedIntFlag(IntFlag): FOO = ... +[builtins fixtures/enum.pyi] -reveal_type(InheritedIntFlag.FOO) # N: Revealed type is "Literal[__main__.InheritedIntFlag.FOO]?" -reveal_type(InheritedIntFlag.FOO.value) # N: Revealed type is "builtins.int" -reveal_type(InheritedIntFlag.FOO._value_) # N: Revealed type is "builtins.int" +[case testSunderValueTypeEllipsisNonStub] +from enum import Enum, StrEnum + +class Basic(Enum): + _value_: int + FOO = 1 + +reveal_type(Basic.FOO) # N: Revealed type is "Literal[__main__.Basic.FOO]?" +reveal_type(Basic.FOO.value) # N: Revealed type is "Literal[1]?" +reveal_type(Basic.FOO._value_) # N: Revealed type is "builtins.int" + +# TODO: this and below should produce diagnostics, Ellipsis is not assignable to int +# Now we do not check members against _value_ at all. + +class FromStub(Enum): + _value_: int + FOO = ... + +reveal_type(FromStub.FOO) # N: Revealed type is "Literal[__main__.FromStub.FOO]?" +reveal_type(FromStub.FOO.value) # N: Revealed type is "builtins.ellipsis" +reveal_type(FromStub.FOO._value_) # N: Revealed type is "builtins.int" + +class InheritedStr(StrEnum): + FOO = ... + +reveal_type(InheritedStr.FOO) # N: Revealed type is "Literal[__main__.InheritedStr.FOO]?" +reveal_type(InheritedStr.FOO.value) # N: Revealed type is "builtins.ellipsis" +reveal_type(InheritedStr.FOO._value_) # N: Revealed type is "builtins.ellipsis" [builtins fixtures/enum.pyi] From 810504dec56efb572065d18ce8f4382f5745b142 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sun, 29 Jun 2025 16:17:06 +0200 Subject: [PATCH 6/8] Refactor --- mypy/plugins/enums.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/mypy/plugins/enums.py b/mypy/plugins/enums.py index 6819061d3196..9a6a438f8ccf 100644 --- a/mypy/plugins/enums.py +++ b/mypy/plugins/enums.py @@ -91,17 +91,14 @@ def _infer_value_type_with_auto_fallback( # Enums in stubs may have ... instead of actual values. If `_value_` is annotated # (manually or inherited from IntEnum, for example), it is a more reasonable guess # than literal ellipsis type. - source_module = ctx.api.modules[ctx.type.type.fullname.rsplit(".", 1)[0]] if ( - source_module.is_stub + _is_defined_in_stub(ctx) and isinstance(proper_type, Instance) and proper_type.type.fullname in ELLIPSIS_TYPE_NAMES + and isinstance(ctx.type, Instance) ): - if ( - isinstance(ctx.type, Instance) - and (value_type := ctx.type.type.get("_value_")) - and isinstance(var := value_type.node, Var) - ): + value_type = ctx.type.type.get("_value_") + if value_type is not None and isinstance(var := value_type.node, Var): return var.type return proper_type if not (isinstance(proper_type, Instance) and proper_type.type.fullname == "enum.auto"): @@ -131,6 +128,13 @@ def _infer_value_type_with_auto_fallback( return ctx.default_attr_type +def _is_defined_in_stub(ctx: mypy.plugin.AttributeContext) -> bool: + return ( + isinstance(ctx.type, Instance) + and ctx.api.modules[ctx.type.type.fullname.rsplit(".", 1)[0]].is_stub + ) + + def _implements_new(info: TypeInfo) -> bool: """Check whether __new__ comes from enum.Enum or was implemented in a subclass. In the latter case, we must infer Any as long as mypy can't infer From 02eac7076f8fe812c16fc9ac821e227c40b7922e Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sun, 29 Jun 2025 16:22:55 +0200 Subject: [PATCH 7/8] Fix selfcheck --- mypy/plugins/enums.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy/plugins/enums.py b/mypy/plugins/enums.py index 9a6a438f8ccf..4586740de031 100644 --- a/mypy/plugins/enums.py +++ b/mypy/plugins/enums.py @@ -17,6 +17,7 @@ from typing import Final, TypeVar, cast import mypy.plugin # To avoid circular imports. +from mypy.checker import TypeChecker from mypy.nodes import TypeInfo, Var from mypy.semanal_enum import ENUM_BASES from mypy.subtypes import is_equivalent @@ -129,6 +130,7 @@ def _infer_value_type_with_auto_fallback( def _is_defined_in_stub(ctx: mypy.plugin.AttributeContext) -> bool: + assert isinstance(ctx.api, TypeChecker) return ( isinstance(ctx.type, Instance) and ctx.api.modules[ctx.type.type.fullname.rsplit(".", 1)[0]].is_stub From 6a7cd8874b35d08a39265fb9071749ee91f7c9cb Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sun, 29 Jun 2025 17:25:39 +0200 Subject: [PATCH 8/8] Use dedicated attribute to support nested definitions --- mypy/plugins/enums.py | 5 +---- test-data/unit/check-enum.test | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/mypy/plugins/enums.py b/mypy/plugins/enums.py index 4586740de031..86c0899ff9ee 100644 --- a/mypy/plugins/enums.py +++ b/mypy/plugins/enums.py @@ -131,10 +131,7 @@ def _infer_value_type_with_auto_fallback( def _is_defined_in_stub(ctx: mypy.plugin.AttributeContext) -> bool: assert isinstance(ctx.api, TypeChecker) - return ( - isinstance(ctx.type, Instance) - and ctx.api.modules[ctx.type.type.fullname.rsplit(".", 1)[0]].is_stub - ) + return isinstance(ctx.type, Instance) and ctx.api.modules[ctx.type.type.module_name].is_stub def _implements_new(info: TypeInfo) -> bool: diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index ae27d1bafa35..d034fe1a6f5f 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2536,7 +2536,8 @@ def check(thing: Things) -> None: [case testSunderValueTypeEllipsis] from foo.bar import ( - Basic, FromStub, InheritedInt, InheritedStr, InheritedFlag, InheritedIntFlag + Basic, FromStub, InheritedInt, InheritedStr, InheritedFlag, + InheritedIntFlag, Wrapper ) reveal_type(Basic.FOO) # N: Revealed type is "Literal[foo.bar.Basic.FOO]?" @@ -2547,6 +2548,10 @@ reveal_type(FromStub.FOO) # N: Revealed type is "Literal[foo.bar.FromStub.FOO]? reveal_type(FromStub.FOO.value) # N: Revealed type is "builtins.int" reveal_type(FromStub.FOO._value_) # N: Revealed type is "builtins.int" +reveal_type(Wrapper.Nested.FOO) # N: Revealed type is "Literal[foo.bar.Wrapper.Nested.FOO]?" +reveal_type(Wrapper.Nested.FOO.value) # N: Revealed type is "builtins.int" +reveal_type(Wrapper.Nested.FOO._value_) # N: Revealed type is "builtins.int" + reveal_type(InheritedInt.FOO) # N: Revealed type is "Literal[foo.bar.InheritedInt.FOO]?" reveal_type(InheritedInt.FOO.value) # N: Revealed type is "builtins.int" reveal_type(InheritedInt.FOO._value_) # N: Revealed type is "builtins.int" @@ -2575,6 +2580,11 @@ class FromStub(Enum): _value_: int FOO = ... +class Wrapper: + class Nested(Enum): + _value_: int + FOO = ... + class InheritedInt(IntEnum): FOO = ... @@ -2616,4 +2626,12 @@ class InheritedStr(StrEnum): reveal_type(InheritedStr.FOO) # N: Revealed type is "Literal[__main__.InheritedStr.FOO]?" reveal_type(InheritedStr.FOO.value) # N: Revealed type is "builtins.ellipsis" reveal_type(InheritedStr.FOO._value_) # N: Revealed type is "builtins.ellipsis" + +class Wrapper: + class Nested(StrEnum): + FOO = ... + +reveal_type(Wrapper.Nested.FOO) # N: Revealed type is "Literal[__main__.Wrapper.Nested.FOO]?" +reveal_type(Wrapper.Nested.FOO.value) # N: Revealed type is "builtins.ellipsis" +reveal_type(Wrapper.Nested.FOO._value_) # N: Revealed type is "builtins.ellipsis" [builtins fixtures/enum.pyi]