From fc0899f3a6e43ae2fdb3306c613750a617d17303 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Mon, 15 Apr 2019 22:40:16 -0700 Subject: [PATCH 1/5] PEP 586: Rewrite sections regarding enum This commit adjusts two sections of this PEP that are related to enums. First, it removes the sections regarding the interaction between enums, imports, and Any. I wasn't aware that the import behavior described in that section was mypy-only and isn't codified in PEP 484. So, I decided to just remove that section entirely -- it didn't feel there was much I could salvage there. Instead, I opted to adjust the "invalid parameters" section to explain in a little more detail why `Literal[Any]` is not allowed. Second, I split up the section about type narrowing into two. The first new section is a reminder that PEP 484 requires type checkers to support certain kinds of exhaustibility checks when working with enums. To make this more clear, I adjusted the example to be more closer to what is used in the spec and removed any mention of reachability -- it felt like a distraction. The second section focuses back on some neat tricks using Literals that type checkers may optionally implement. I also tweaked some of the examples here as suggested in https://github.com/python/peps/pull/993. --- pep-0586.rst | 82 ++++++++++++++++++++++------------------------------ 1 file changed, 35 insertions(+), 47 deletions(-) diff --git a/pep-0586.rst b/pep-0586.rst index eb0ce27394f..aece6603593 100644 --- a/pep-0586.rst +++ b/pep-0586.rst @@ -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 be values only. It is also unclear what ``Literal[Any]`` would + actually semantically mean. Parameters at runtime --------------------- @@ -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 ============== @@ -514,20 +496,21 @@ 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 --------------------------------- +Literal types, 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: @@ -535,24 +518,29 @@ final ``else`` statement in the following function is unreachable:: 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 `_. 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 predicted +complexity of the implementation work. + +Some of this complexity will be alleviated once Literal types are introduced: +rather then entirely special-casing enums, we can instead treat them as being +approximately equal 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 equal +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. +Literal types and 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:: @@ -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) @@ -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) -> Literal[False]: ... def is_int_like(x): ... vector: List[int] = [1, 2, 3] From 5cc5ba3b34fe16c5ff3b90ed8adecae189a0c962 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 16 Apr 2019 09:18:51 -0700 Subject: [PATCH 2/5] Apply typo fix and wording suggestions Co-Authored-By: Michael0x2a --- pep-0586.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pep-0586.rst b/pep-0586.rst index aece6603593..7932225c3c6 100644 --- a/pep-0586.rst +++ b/pep-0586.rst @@ -527,12 +527,12 @@ checkers (such as mypy) do not yet implement this due to the predicted complexity of the implementation work. Some of this complexity will be alleviated once Literal types are introduced: -rather then entirely special-casing enums, we can instead treat them as being -approximately equal to the union of their values and take advantage of any +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 equal +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. From 7a67c1d69613a88eba7659f2446660b0848fb505 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Tue, 16 Apr 2019 09:21:00 -0700 Subject: [PATCH 3/5] Remove trailing whitespace --- pep-0586.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0586.rst b/pep-0586.rst index 3dd3a150d5d..8c1f5fc889e 100644 --- a/pep-0586.rst +++ b/pep-0586.rst @@ -550,7 +550,7 @@ containment or equality checks:: # Type checker could narrow 'status' to type # Literal["MALFORMED", "ABORTED"] here. return expects_bad_status(status) - + # Similarly, type checker could narrow 'status' to Literal["PENDING"] if status == "PENDING": expects_pending_status(status) From d5bf32fd321ac9e657ab2dd3ba5dd447123b85b5 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Tue, 16 Apr 2019 09:30:27 -0700 Subject: [PATCH 4/5] Make subsection titles more consistent; tweak wording --- pep-0586.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pep-0586.rst b/pep-0586.rst index 8c1f5fc889e..bbc7587e4c6 100644 --- a/pep-0586.rst +++ b/pep-0586.rst @@ -255,8 +255,8 @@ allowing them in future extensions of this PEP. behavior based on a float parameter. - Any: e.g. ``Literal[Any]``. ``Any`` is a type, and ``Literal[...]`` is - meant to be values only. It is also unclear what ``Literal[Any]`` would - actually semantically mean. + meant to contain values only. It is also unclear what ``Literal[Any]`` + would actually semantically mean. Parameters at runtime --------------------- @@ -496,8 +496,8 @@ 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. -Literal types, enums, and exhaustiveness checks ------------------------------------------------ +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 @@ -536,7 +536,7 @@ 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. -Literal types and narrowing +Interactions with narrowing --------------------------- Type checkers may optionally perform additional analysis for both enum and From 0d886344c2c45c1e8962da75c98a136969da5966 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Wed, 17 Apr 2019 18:50:34 -0700 Subject: [PATCH 5/5] Respond to code review --- pep-0586.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0586.rst b/pep-0586.rst index bbc7587e4c6..832570945a2 100644 --- a/pep-0586.rst +++ b/pep-0586.rst @@ -523,7 +523,7 @@ values of the ``Status`` enum have already been exhausted:: The interaction described above is not new: it's already `already codified within PEP 484 `_. However, many type -checkers (such as mypy) do not yet implement this due to the predicted +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: @@ -562,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: object) -> Literal[False]: ... + def is_int_like(x: object) -> bool: ... def is_int_like(x): ... vector: List[int] = [1, 2, 3]