Search code examples
pythonpycharmtype-hinting

Overload type hint with Literal and Enum not working in PyCharm


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


Solution

  • 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.