Skip to content

Eager narrowing to Literal when possible #15434

Open

Description

@harahu

Feature

I often feel I have to put too much effort into informing mypy that some variables can only hold a finite set of values. It would be helpful if mypy was a bit smarter in the way it infers Literal types.

Pitch

Here are some concrete examples:

from typing import Literal

def letter_v1(number: int) -> Literal["a", "b", "c"]:
    if number == 1:
        letter = "a"
    elif number == 2:
        letter = "b"
    else:
        letter = "c"
    
    # Incompatible return value type (got "str", expected "Literal['a', 'b', 'c']")  [return-value]
    return letter
    
def letter_v2(number: int) -> Literal["a", "b", "c"]:
    letter = "a" if number == 1 else "b" if number == 2 else "c"
    
    # Incompatible return value type (got "str", expected "Literal['a', 'b', 'c']")  [return-value]
    return letter

def letter_v3(number: int) -> Literal["a", "b", "c"]:
    letter = ("a", "b", "c")[number]
    
    # Incompatible return value type (got "str", expected "Literal['a', 'b', 'c']")  [return-value]
    return letter

See also: https://mypy-play.net/?mypy=1.3.0&python=3.11&flags=strict&gist=bab569e05877621b90f081fc7bdb7d05

Ideally, I think I should get away without having to specify the type of letter in any of the concrete examples here. A human reviewing this code would easily conclude that there is no issue here, yet mypy struggles.

Activity

  1. erictraut commented on Jun 14, 2023

    @erictraut

    There are two separate issues that prevent mypy from type checking this code without errors.

    1. Mypy doesn't narrow types to literals upon assignment. That means the type of letter will not be narrowed to Literal["a"] after the statement letter = "a".
    2. Mypy uses joins rather than unions when combining types during code flow analysis. Even if mypy were to narrow types to literals upon assignments, it would combine Literal["a"], Literal["b"] and Literal["c"] into str rather than Literal["a", "b", "c"].

    I've been lobbying for item 2 to be changed for quite a while. If you filter issues on the tag "topic-join-vs-union", you will see that there are more than 50 open issues that would be addressed by this change.

    For item 1, I think it's unlikely that the developers of mypy would make that change. It would be too disruptive to existing mypy users, and there are other many other design decisions that would need to be modified as well (e.g. inference of list, set, dict and tuple expressions that include literals).

    FWIW, the above code type checks fine in pyright.

  2. harahu commented on Jun 15, 2023

    @harahu
    Author

    Thanks @erictraut for sharing your insights on this. It is good to get a sense of the design decisions that led us here.

    As an aside, I wonder if it could be possible to use the return signature of the function as an upper bound for any variable returned from it. That is, having the type checker infer that -> Literal["a", "b", "c"] also implies that the developer intends letter to at least be no wider typed than Literal["a", "b", "c"], since return letter is present in the function.

    This approach might not be worth anything if your suggestions are implemented, but if not, this might be a third approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions