Skip to content

Edge case of invalid TypeVar inference with Generic #16075

Closed
@francis-clairicia

Description

@francis-clairicia

Bug Report

It is not possible to infer the type of a TypeVar declared in a Generic with another TypeVar in __init__ by adding a type hint to self.

To Reproduce

from typing import Any, Generic, TypeVar, overload

DTOPacketT = TypeVar("DTOPacketT")

SentPacketT = TypeVar("SentPacketT")
ReceivedPacketT = TypeVar("ReceivedPacketT")

class Serializer(Generic[DTOPacketT]):
    ...
    

class Converter(Generic[SentPacketT, ReceivedPacketT, DTOPacketT]):
    ...
    

class Protocol(Generic[SentPacketT, ReceivedPacketT]):
    @overload
    def __init__(
        self: Protocol[DTOPacketT, DTOPacketT],
        serializer: Serializer[DTOPacketT],
        converter: None = ...,
    ) -> None:
        ...

    @overload
    def __init__(
        self,
        serializer: Serializer[DTOPacketT],
        converter: Converter[SentPacketT, ReceivedPacketT, DTOPacketT],
    ) -> None:
        ...

    def __init__(
        self,
        serializer: Serializer[Any],
        converter: Converter[SentPacketT, ReceivedPacketT, Any] | None = None,
    ) -> None:
        ...

# This case works
serializer: Serializer[str] = Serializer()
converter: Converter[dict[str, Any], dict[str, Any], str] = Converter()

protocol_with_converter = Protocol(serializer, converter=converter)
reveal_type(protocol_with_converter)  # Revealed type is "__main__.Protocol[builtins.dict[builtins.str, Any], builtins.dict[builtins.str, Any]]"

# This case fails
protocol_without_converter = Protocol(serializer, converter=None)
reveal_type(protocol_without_converter)

There is a mypy-play available here:

https://mypy-play.net/?mypy=latest&python=3.11&gist=282865693449e3786dde94d2f45bfb30

Expected Behavior
protocol_without_converter should be inferred as Protocol[str, str]

Actual Behavior

main.py:49: error: Argument 1 to "Protocol" has incompatible type "Serializer[str]"; expected "Serializer[<nothing>]"  [arg-type]
main.py:50: note: Revealed type is "__main__.Protocol[DTOPacketT`-1, DTOPacketT`-1]"

EDIT: A workaround to get this to work is to do the following:

    @overload
    def __init__(
        self,
        serializer: Serializer[SentPacketT | ReceivedPacketT],
        converter: None = ...,
    ) -> None:
        ...

Mypy playground: https://mypy-play.net/?mypy=latest&python=3.11&gist=77ee74bfd5e80a91e3c0ff654a502d1f

Your Environment

  • Mypy version used: 1.5.1
  • Mypy command-line flags: (none)
  • Mypy configuration options from mypy.ini (and other config files): (none)
  • Python version used: 3.11.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions