python / python/mypy

overload not working with Generic type

Open
#8,800 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug priority-2-low
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

I'm running into an issue where a function overload is not working; I get no errors from mypy and one of the overload branches returns the proper type, but the other is returning Any instead of the type I would expect.

I've distilled it down to the snippet below. To reproduce, paste the following into a file and simply run mypy <filename>.
I'm running mypy version 0.770 on a Mac with homebrew Python 3.7.7

from typing import TypeVar, overload, Type, Optional, Generic


# Define a Player base class that can be paired with some data.
T = TypeVar('T')

class Player(Generic[T]):
    pass


# Create an overloaded function to handle casting
# both Player and Optional[Player] to corresponding
# specific subtypes.
# (to ensure Optional state stays intact, unlike raw cast())

PT = TypeVar('PT', bound=Player)

@overload
def playercast(totype: Type[PT], obj: Player) -> PT:
    ...

@overload
def playercast(totype: Optional[Type[PT]],
               obj: Optional[Player]) -> Optional[PT]:
    ...

def playercast(totype: Optional[Type[PT]],
               obj: Optional[Player]) -> Optional[PT]:
    return obj  # type: ignore


# Now define a PlayerConcrete subtype, assign a few to Player vars,
# and then attempt to cast them back to PlayerConcrete.

class PlayerConcrete(Player[int]):
    pass

player1: Optional[Player] = PlayerConcrete()
player2: Player = PlayerConcrete()

# Gives Optional[PlayerConcrete] as I would expect.
reveal_type(playercast(PlayerConcrete, player1))

# This, however, gives Any.
reveal_type(playercast(PlayerConcrete, player2))



# Doing the same thing with distinct functions works as expected.

def playercast_nonopt(totype: Type[PT], obj: Player) -> PT:
    return obj  # type: ignore

def playercast_opt(totype: Optional[Type[PT]],
                   obj: Optional[Player]) -> Optional[PT]:
    return obj  # type: ignore

# Gives Optional[PlayerConcrete].
reveal_type(playercast_opt(PlayerConcrete, player1))

# Gives PlayerConcrete.
reveal_type(playercast_nonopt(PlayerConcrete, player2))

Expected results:

  • I would expect the second reveal_type() to return PT (ConcretePlayer in this case)

Actual results:

  • The second reveal_type() returns Any

Should I expect this case to be functional? I've found that if Player is not a Generic class then it works. Is that a known limitation of overloads such as this, or is there a way to get this to work?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the supplied Python snippet with mypy 0.770 and compare the two reveal_type results, especially the generic Player overload. Trace the overload and type-inference behavior involved in the PlayerConcrete call; done means the non-optional call reports PlayerConcrete rather than Any while the existing optional behavior remains correct.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.