python / python/mypy

Incorrect type inferencing when parameterizing with Self-like type variable involving `__ge__`

Open
#13,864 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

Found a very strange bug involving self-like type variables while using __ge__ and similar methods. The following example defines:

  • a parameterized protocol SupportsComparison which takes Self as its first parameter and the type being compared to with its other parameter,
  • a protocol SupportsRadd which allows type-hinting type(t + supports_radd) == type(t),
  • a function foo should then accept foo(x, y, z) where x supports comparison with y and z supports radd with x and returns (x, y).

In the example, foo(float, float, Fraction) -> tuple[float, float] is the expected result, since:

  • float supports comparison with float and self should be float, meaning x: SupportsComparison[float, float],
  • Fraction supports radd with float, meaning z: SupportsRadd[float].

However, the result given by reveal_type(...) gives tuple[Fraction, float]. How is this the case?

from typing import Any, Protocol, TypeVar

T = TypeVar("T")
T_con = TypeVar("T_con", contravariant=True)

Self = TypeVar("Self", bound="SupportsComparison[Any, Any]", covariant=True)


class SupportsComparison(Protocol[Self, T_con]):

    def __ge__(self: Self, other: T_con) -> bool: ...
    def __gt__(self: Self, other: T_con) -> bool: ...
    def __le__(self: Self, other: T_con) -> bool: ...
    def __lt__(self: Self, other: T_con) -> bool: ...


class SupportsRadd(Protocol[T]):

    def __radd__(self, other: T) -> T: ...


X = TypeVar("X", bound=SupportsComparison[Any, Any])
Y = TypeVar("Y")
Z = TypeVar("Z", bound=SupportsRadd[Any])

def foo(
    x: SupportsComparison[X, Y],
    y: Y,
    z: SupportsRadd[X],
) -> tuple[X, Y]:
    return (x, y)

from fractions import Fraction

reveal_type(foo(float(), float(), Fraction()))  # Revealed type is "Tuple[fractions.Fraction*, builtins.float*]"
  • Mypy version: 0.942.
  • Python version used: 3.10.0.

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 reproducer with the current mypy version and tracing inference for foo, especially the Self-like SupportsComparison parameter and ge methods. Locate the relevant type-inference tests or add a regression test for the example; done means reveal_type reports tuple[float, float] rather than tuple[Fraction, float].

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
Mostly clear
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.