Overload type hint with Literal and Enum not working in PyCharm

Question:

I want to typehint an overload function. For that I use the overload decorator from typing. I want to set multiple possible callees based on a parameter’s value. This parameter is color.

I have this code:

from typing import Literal, overload
from enum import Enum

class Color(Enum):
    RED = 0
    BLUE = 1
    GREEN = 2

@overload
def func(
    *,
    title: str,
    color: Literal[Color.RED, Color.BLUE],
    description: str,
) -> None:
    ...

@overload
def func(
    *,
    title: str,
    color: Literal[Color.GREEN],
) -> None:
    ...

def func(
    *,
    title: str = None,
    color: Color = None,
    description: str = None,
) -> None:
    print(title, color, description)


func(title="hello", color=Color.GREEN, description="hello")

I want to get a warning when I try to set the description, even the color is set to Color.GREEN, but I don’t get a warning:

no_warning_enum

When I do the same just with strings, it works. I replaced the Literals with Literal["red", "blue"] and Literal["green"] and changed the type of color to str:

warning_strings

Accordingly, there is no error, when I don’t try to set description, which I expect:

no_warnings_strings

Python Version: 3.8
IDE: PyCharm

Asked By: puncher

||

Answers:

You discovered a pycharm bug, here’s the corresponding issue. There is no workaround suggested, and I doubt there can be one.

mypy handles such overload case properly, also pointing out missing | None in the implementation signature (title: str = None is usually a bad thing to write, explicit is better than implicit, and mypy has hidden the decision to infer such type implicitly under a configuration flag for this reason – such thing was enabled by default earlier). Here’s a playground link to check.

To fix the implicit-optional issue mentioned above, you could do the following (Optional[X] is equivalent to Union[X, None], where X is some valid type):

from typing import Optional
...
# Overloads here

def func(
    *,
    title: Optional[str] = None,
    color: Optional[Color] = None,
    description: Optional[str] = None,
) -> None:
    print(title, color, description)

or, on python 3.10 and higher or with annotations future-import,

...
# Overloads here

def func(
    *,
    title: str | None = None,
    color: Color | None = None,
    description: str | None = None,
) -> None:
    print(title, color, description)

Finally, as long as you always require title and color in both overloads, why not make them required in implementation (note that first two args don’t have to be Optional any more)?

from typing import Optional
...
# Overloads here

def func(
    *,
    title: str,
    color: Color,
    description: Optional[str] = None,
) -> None:
    print(title, color, description)

Your usage is absolutely valid, and it’s just a bug in your IDE.

Answered By: SUTerliakov
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.