Skip to content

PEP 586: Rewrite sections regarding enum #997

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 6 commits into from
Apr 18, 2019
Merged
Changes from all commits
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
82 changes: 35 additions & 47 deletions pep-0586.rst
Original file line number Diff line number Diff line change
Expand Up @@ -248,13 +248,15 @@ The following parameters are intentionally disallowed by design:
only types, never over values.

The following are provisionally disallowed for simplicity. We can consider
allowing them on a case-by-case basis based on demand.
allowing them in future extensions of this PEP.

- Floats: e.g. ``Literal[3.14]``. Note: if we do decide to allow
floats, we should likely disallow literal infinity and literal NaN.

- Any: e.g. ``Literal[Any]`` Note: the semantics of what exactly
``Literal[Any]`` means would need to be clarified first.
- Floats: e.g. ``Literal[3.14]``. Representing Literals of infinity or NaN
in a clean way is tricky; real-world APIs are unlikely to vary their
behavior based on a float parameter.

- Any: e.g. ``Literal[Any]``. ``Any`` is a type, and ``Literal[...]`` is
meant to contain values only. It is also unclear what ``Literal[Any]``
would actually semantically mean.

Parameters at runtime
---------------------
Expand Down Expand Up @@ -292,26 +294,6 @@ In cases like these, we always assume the user meant to construct a
literal string. If the user wants a forward reference, they must wrap
the entire literal type in a string -- e.g. ``"Literal[Color.RED]"``.

Literals, enums, and Any
------------------------

Another ambiguity is when the user attempts to use some expression that
is meant to be an enum but is actually of type ``Any``. For example,
suppose a user attempts to import an enum from a package with no type hints::

from typing import Literal
from lib_with_no_types import SomeEnum # SomeEnum has type 'Any'!

# x has type `Literal[Any]` due to the bad import
x: Literal[SomeEnum.FOO]

Because ``Literal`` may not be parameterized by ``Any``, this program
is *illegal*: the type checker should report an error with the last line.

In short, while ``Any`` may effectively be used as a placeholder for any
arbitrary *type*, it is currently **not** allowed to serve as a placeholder
for any arbitrary *value*.

Type inference
==============

Expand Down Expand Up @@ -514,45 +496,51 @@ We considered several different proposals for fixing this, but ultimately
decided to defer the problem of integer generics to a later date. See
`Rejected or out-of-scope ideas`_ for more details.

Interactions with type narrowing
--------------------------------
Interactions with enums and exhaustiveness checks
-------------------------------------------------

Type checkers should be capable of performing exhaustiveness checks when
working Literal types that have a closed number of variants, such as
enums. For example, the type checker should be capable of inferring that the
final ``else`` statement in the following function is unreachable::
enums. For example, the type checker should be capable of inferring that
the final ``else`` statement must be of type ``str``, since all three
values of the ``Status`` enum have already been exhausted::

class Status(Enum):
SUCCESS = 0
INVALID_DATA = 1
FATAL_ERROR = 2

def parse_status(s: Status) -> None:
def parse_status(s: Union[str, Status]) -> None:
if s is Status.SUCCESS:
print("Success!")
elif s is Status.INVALID_DATA:
print("The given data is invalid because...")
elif s is Status.FATAL_ERROR:
print("Unexpected fatal error...")
else:
# Error should not be reported by type checkers that
# ignore errors in unreachable blocks
print("Nonsense" + 100)
# 's' must be of type 'str' since all other options are exhausted
print("Got custom status: " + s)

This behavior is technically not new: this behavior is
The interaction described above is not new: it's already
`already codified within PEP 484 <pep-484-enums_>`_. However, many type
checkers (such as mypy) do not yet implement this behavior. Once Literal
types are introduced, it will become easier to do so: we can model
enums as being approximately equal to the union of their values and
take advantage of any existing logic regarding unions, exhaustibility,
and type narrowing.

So here, ``Status`` could be treated as being approximately equal to
``Literal[Status.SUCCESS, Status.INVALID_DATA, Status.FATAL_ERROR]``
checkers (such as mypy) do not yet implement this due to the expected
complexity of the implementation work.

Some of this complexity will be alleviated once Literal types are introduced:
rather than entirely special-casing enums, we can instead treat them as being
approximately equivalent to the union of their values and take advantage of any
existing logic regarding unions, exhaustibility, type narrowing, reachability,
and so forth the type checker might have already implemented.

So here, the ``Status`` enum could be treated as being approximately equivalent
to ``Literal[Status.SUCCESS, Status.INVALID_DATA, Status.FATAL_ERROR]``
and the type of ``s`` narrowed accordingly.

Type checkers may optionally perform additional analysis and narrowing
beyond what is described above.
Interactions with narrowing
---------------------------

Type checkers may optionally perform additional analysis for both enum and
non-enum Literal types beyond what is described in the section above.

For example, it may be useful to perform narrowing based on things like
containment or equality checks::
Expand All @@ -563,7 +551,7 @@ containment or equality checks::
# Literal["MALFORMED", "ABORTED"] here.
return expects_bad_status(status)

# Similarly, type checker could narrow 'x' to Literal["PENDING"]
# Similarly, type checker could narrow 'status' to Literal["PENDING"]
if status == "PENDING":
expects_pending_status(status)

Expand All @@ -574,7 +562,7 @@ involving Literal bools. For example, we can combine ``Literal[True]``,
@overload
def is_int_like(x: Union[int, List[int]]) -> Literal[True]: ...
@overload
def is_int_like(x: Union[str, List[str]]) -> Literal[False]: ...
def is_int_like(x: object) -> bool: ...
def is_int_like(x): ...

vector: List[int] = [1, 2, 3]
Expand Down