Got it, thanks. On Sat, 2021-09-18 at 21:08 -0600, Carl Meyer wrote:
Hi Paul,
On Sat, Sep 18, 2021 at 11:24 AM Paul Bryan
wrote: What seems problematic to me about this is that static and dynamic type checking become mutually exclusive; the f(x+1) example.
I'm not sure "mutually exclusive" is a good description of the situation, since I think in practice there is pretty much always _some_ way to write the code that makes both happy. But it is undoubtedly true (and inherent to the nature of static analysis) that static type checks must sometimes reject code that would pass runtime type checks. In a sound type system the reverse _should_ not be true (runtime type checks should not fail on code that passes static type checks), but Python's type system is not sound (a type system can't be both sound and gradual without adding runtime checks; the Any type is unsound), so it is also possible in Python for code to pass static type checks and still fail runtime type checks. The existence of both of these possibilities is embedded in the nature of Python typing and is not specifically related to Literal types.
I'm not conversant enough with static type checking practices to know: how should one pass a value to a Literal when the value being passed is not static? For example, if mode were added to open(..., mode: Literal["r", "w", "r+", "w+", ...]) and mode were dynamically generated, how would one express it so as to pass muster with mypy?
The only reason typing open's mode argument with a Literal _might_ be considered is that it is so unlikely anyone would want to pass a dynamic value for it, since the code that uses the resulting file object is so likely to be itself dependent on the specific mode. But if there were some realistic use case for this, the options are to either add runtime conditionals that allow your static type checker to narrow its statically known type to a Literal type (e.g. something along the lines of `if type(x) is str and x in ["r", "w", "r+", "w+"]:`), or if your static type checker doesn't have advanced enough narrowing to do this, use a cast.
def f(x: Literal["A", "B", "C", 1, 2, 3]) -> None: if isinstance(x, int): g(cast(Literal[1, 2, 3], x))
def g(x: Literal[1, 2, 3]) -> None:
This is a case that static type checkers should be able to handle; if they know x is a `Literal["A", "B", "C", 1, 2, 3]`, and then they know that x is also an int, they can narrow its type to `Literal[1, 2, 3]`, and no cast should ideally be needed here. (I haven't surveyed mypy, Pyre, and pyright to find out if they actually do handle this specific case, though Eric's message suggests that pyright at least probably does.)
Carl