Skip to content

Commit 861f01c

Browse files
TH3CHARLieJukkaL
authored andcommitted
Don't make underscored attributes as enum members (#8302)
Fixes #5312. From @JelleZijlstra 's description, for underscored attributes, we don't make them as enum members.
1 parent c784e3b commit 861f01c

File tree

2 files changed

+51
-13
lines changed

2 files changed

+51
-13
lines changed

mypy/checkmember.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -705,19 +705,9 @@ def analyze_class_attribute_access(itype: Instance,
705705
check_final_member(name, info, mx.msg, mx.context)
706706

707707
if info.is_enum and not (mx.is_lvalue or is_decorated or is_method):
708-
# Skip "_order_" and "__order__", since Enum will remove it
709-
if name in ("_order_", "__order__"):
710-
return mx.msg.has_no_attr(
711-
mx.original_type, itype, name, mx.context, mx.module_symbol_table
712-
)
713-
714-
enum_literal = LiteralType(name, fallback=itype)
715-
# When we analyze enums, the corresponding Instance is always considered to be erased
716-
# due to how the signature of Enum.__new__ is `(cls: Type[_T], value: object) -> _T`
717-
# in typeshed. However, this is really more of an implementation detail of how Enums
718-
# are typed, and we really don't want to treat every single Enum value as if it were
719-
# from type variable substitution. So we reset the 'erased' field here.
720-
return itype.copy_modified(erased=False, last_known_value=enum_literal)
708+
enum_class_attribute_type = analyze_enum_class_attribute_access(itype, name, mx)
709+
if enum_class_attribute_type:
710+
return enum_class_attribute_type
721711

722712
t = node.type
723713
if t:
@@ -815,6 +805,28 @@ def analyze_class_attribute_access(itype: Instance,
815805
return typ
816806

817807

808+
def analyze_enum_class_attribute_access(itype: Instance,
809+
name: str,
810+
mx: MemberContext,
811+
) -> Optional[Type]:
812+
# Skip "_order_" and "__order__", since Enum will remove it
813+
if name in ("_order_", "__order__"):
814+
return mx.msg.has_no_attr(
815+
mx.original_type, itype, name, mx.context, mx.module_symbol_table
816+
)
817+
# For other names surrendered by underscores, we don't make them Enum members
818+
if name.startswith('__') and name.endswith("__") and name.replace('_', '') != '':
819+
return None
820+
821+
enum_literal = LiteralType(name, fallback=itype)
822+
# When we analyze enums, the corresponding Instance is always considered to be erased
823+
# due to how the signature of Enum.__new__ is `(cls: Type[_T], value: object) -> _T`
824+
# in typeshed. However, this is really more of an implementation detail of how Enums
825+
# are typed, and we really don't want to treat every single Enum value as if it were
826+
# from type variable substitution. So we reset the 'erased' field here.
827+
return itype.copy_modified(erased=False, last_known_value=enum_literal)
828+
829+
818830
def add_class_tvars(t: ProperType, isuper: Optional[Instance],
819831
is_classmethod: bool,
820832
original_type: Type,

test-data/unit/check-enum.test

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,3 +1136,29 @@ else:
11361136
reveal_type(x4) # N: Revealed type is '__main__.Foo'
11371137
reveal_type(x5) # N: Revealed type is '__main__.Foo'
11381138
[builtins fixtures/primitives.pyi]
1139+
1140+
[case testPrivateAttributeNotAsEnumMembers]
1141+
import enum
1142+
1143+
class Comparator(enum.Enum):
1144+
LessThan = "<"
1145+
LessThanOrEqualTo = "<="
1146+
EqualTo = "=="
1147+
NotEqualTo = "!="
1148+
GreaterThanOrEqualTo = ">="
1149+
GreaterThan = ">"
1150+
1151+
__foo__ = {
1152+
LessThan: 1,
1153+
LessThanOrEqualTo: 2,
1154+
EqualTo: 3,
1155+
NotEqualTo: 4,
1156+
GreaterThanOrEqualTo: 5,
1157+
GreaterThan: 6,
1158+
}
1159+
1160+
def foo(self) -> int:
1161+
return Comparator.__foo__[self.value]
1162+
1163+
reveal_type(Comparator.__foo__) # N: Revealed type is 'builtins.dict[builtins.str, builtins.int]'
1164+
[builtins fixtures/dict.pyi]

0 commit comments

Comments
 (0)