Skip to content

Commit

Permalink
Don't make underscored attributes as enum members (#8302)
Browse files Browse the repository at this point in the history
Fixes #5312.

From @JelleZijlstra 's description, for underscored attributes, we don't make 
them as enum members.
  • Loading branch information
TH3CHARLie authored and JukkaL committed Jan 17, 2020
1 parent c784e3b commit 861f01c
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 13 deletions.
38 changes: 25 additions & 13 deletions mypy/checkmember.py
Expand Up @@ -705,19 +705,9 @@ def analyze_class_attribute_access(itype: Instance,
check_final_member(name, info, mx.msg, mx.context)

if info.is_enum and not (mx.is_lvalue or is_decorated or is_method):
# Skip "_order_" and "__order__", since Enum will remove it
if name in ("_order_", "__order__"):
return mx.msg.has_no_attr(
mx.original_type, itype, name, mx.context, mx.module_symbol_table
)

enum_literal = LiteralType(name, fallback=itype)
# When we analyze enums, the corresponding Instance is always considered to be erased
# due to how the signature of Enum.__new__ is `(cls: Type[_T], value: object) -> _T`
# in typeshed. However, this is really more of an implementation detail of how Enums
# are typed, and we really don't want to treat every single Enum value as if it were
# from type variable substitution. So we reset the 'erased' field here.
return itype.copy_modified(erased=False, last_known_value=enum_literal)
enum_class_attribute_type = analyze_enum_class_attribute_access(itype, name, mx)
if enum_class_attribute_type:
return enum_class_attribute_type

t = node.type
if t:
Expand Down Expand Up @@ -815,6 +805,28 @@ def analyze_class_attribute_access(itype: Instance,
return typ


def analyze_enum_class_attribute_access(itype: Instance,
name: str,
mx: MemberContext,
) -> Optional[Type]:
# Skip "_order_" and "__order__", since Enum will remove it
if name in ("_order_", "__order__"):
return mx.msg.has_no_attr(
mx.original_type, itype, name, mx.context, mx.module_symbol_table
)
# For other names surrendered by underscores, we don't make them Enum members
if name.startswith('__') and name.endswith("__") and name.replace('_', '') != '':
return None

enum_literal = LiteralType(name, fallback=itype)
# When we analyze enums, the corresponding Instance is always considered to be erased
# due to how the signature of Enum.__new__ is `(cls: Type[_T], value: object) -> _T`
# in typeshed. However, this is really more of an implementation detail of how Enums
# are typed, and we really don't want to treat every single Enum value as if it were
# from type variable substitution. So we reset the 'erased' field here.
return itype.copy_modified(erased=False, last_known_value=enum_literal)


def add_class_tvars(t: ProperType, isuper: Optional[Instance],
is_classmethod: bool,
original_type: Type,
Expand Down
26 changes: 26 additions & 0 deletions test-data/unit/check-enum.test
Expand Up @@ -1136,3 +1136,29 @@ else:
reveal_type(x4) # N: Revealed type is '__main__.Foo'
reveal_type(x5) # N: Revealed type is '__main__.Foo'
[builtins fixtures/primitives.pyi]

[case testPrivateAttributeNotAsEnumMembers]
import enum

class Comparator(enum.Enum):
LessThan = "<"
LessThanOrEqualTo = "<="
EqualTo = "=="
NotEqualTo = "!="
GreaterThanOrEqualTo = ">="
GreaterThan = ">"

__foo__ = {
LessThan: 1,
LessThanOrEqualTo: 2,
EqualTo: 3,
NotEqualTo: 4,
GreaterThanOrEqualTo: 5,
GreaterThan: 6,
}

def foo(self) -> int:
return Comparator.__foo__[self.value]

reveal_type(Comparator.__foo__) # N: Revealed type is 'builtins.dict[builtins.str, builtins.int]'
[builtins fixtures/dict.pyi]

0 comments on commit 861f01c

Please sign in to comment.