Skip to content

Support for singleton types in unions with Enum #7693

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 12, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Support for singleton types in unions
  • Loading branch information
ilai-deutel committed Oct 11, 2019
commit 29b9b01b940aee531cc70218aad5f72e07a6bf9e
21 changes: 13 additions & 8 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4751,7 +4751,8 @@ def is_singleton_type(typ: Type) -> bool:
'is_singleton_type(t)' returns True if and only if the expression 'a is b' is
always true.

Currently, this returns True when given NoneTypes and enum LiteralTypes.
Currently, this returns True when given NoneTypes, enum LiteralTypes and
enum types with a single value.

Note that other kinds of LiteralTypes cannot count as singleton types. For
example, suppose we do 'a = 100000 + 1' and 'b = 100001'. It is not guaranteed
Expand All @@ -4761,7 +4762,8 @@ def is_singleton_type(typ: Type) -> bool:
typ = get_proper_type(typ)
# TODO: Also make this return True if the type is a bool LiteralType.
# Also make this return True if the type corresponds to ... (ellipsis) or NotImplemented?
return isinstance(typ, NoneType) or (isinstance(typ, LiteralType) and typ.is_enum_literal())
return (isinstance(typ, NoneType) or (isinstance(typ, LiteralType) and typ.is_enum_literal())
or (isinstance(typ, Instance) and typ.type.is_enum and len(typ.type.names) == 1))
Copy link
Collaborator

@Michael0x2a Michael0x2a Oct 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See other comment about typ.type.names -- the same fix should be done here.



def try_expanding_enum_to_union(typ: Type, target_fullname: str) -> ProperType:
Expand Down Expand Up @@ -4808,17 +4810,20 @@ class Status(Enum):


def coerce_to_literal(typ: Type) -> ProperType:
"""Recursively converts any Instances that have a last_known_value into the
corresponding LiteralType.
"""Recursively converts any Instances that have a last_known_value or are
instances of enum types with a single value into the corresponding LiteralType.
"""
typ = get_proper_type(typ)
if isinstance(typ, UnionType):
new_items = [coerce_to_literal(item) for item in typ.items]
return make_simplified_union(new_items)
elif isinstance(typ, Instance) and typ.last_known_value:
return typ.last_known_value
else:
return typ
elif isinstance(typ, Instance):
if typ.last_known_value:
return typ.last_known_value
elif typ.type.is_enum and len(typ.type.names) == 1:
key, = typ.type.names
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the names dict can also contain reference to methods, which shouldn't be counted as an enum values.

I'd try refactoring out the "get all enum values" check on lines ~4794 to ~4798 of this file into a separate helper function and call it here instead of using typ.type.names directly, and maybe also add a test case making sure mypy doesn't do anything weird if we have an single-value enum that also defines a method.

return LiteralType(value=key, fallback=typ)
return typ


def has_bool_item(typ: ProperType) -> bool:
Expand Down
24 changes: 24 additions & 0 deletions test-data/unit/check-enum.test
Original file line number Diff line number Diff line change
Expand Up @@ -852,5 +852,29 @@ def process(response: Union[str, Reason] = '') -> str:
# response can be only str, all other possible values exhausted
reveal_type(response) # N: Revealed type is 'builtins.str'
return 'PROCESSED: ' + response
[builtins fixtures/primitives.pyi]

[case testEnumReachabilityPEP484Example3]
# flags: --strict-optional
from typing import Union
from typing_extensions import Final
from enum import Enum

class Empty(Enum):
token = 0
_empty = Empty.token

def func(x: Union[int, None, Empty] = _empty) -> int:
boom = x + 42 # E: Unsupported left operand type for + ("None") \
# E: Unsupported left operand type for + ("Empty") \
# N: Left operand is of type "Union[int, None, Empty]"
if x is _empty:
reveal_type(x) # N: Revealed type is 'Literal[__main__.Empty.token]'
return 0
elif x is None:
reveal_type(x) # N: Revealed type is 'None'
return 1
else: # At this point typechecker knows that x can only have type int
reveal_type(x) # N: Revealed type is 'builtins.int'
return x + 2
[builtins fixtures/primitives.pyi]
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy