python / python/mypy

__iadd__ is not applied for += when TypeVars with bound are used.

Offen
#11,745 2 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

bug topic-protocols topic-type-variables
Vorherrschende Sprache
Python
Sterne
20.6k
Forks
3.3k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

Bug Report

Using += with TypeVar bound on Protocol implementing __iadd__ causes error:

Unsupported left operand type for + ("U")

To Reproduce

Define Protocol and generic method as shown below.

# main.py


from __future__ import annotations
from typing import Generic, Protocol, TypeVar     


T = TypeVar("T")
U = TypeVar("U", bound=SupportsIAdd)


class SupportsIAdd(Protocol):
    def __iadd__(self: T, other: T) -> T: ...        


class AddingAccumulatorParam(Generic[U]):
    def addInPlace(self, value1: U, value2: U) -> U:
        value1 += value2  # Fails
        return value1

Expected Behavior

It should pass mypy checks, as U is bound on SupportIAdd which in turn provides __iadd__ that can be used for +=.

Actual Behavior

The snippet shown above fails with:

main.py:17: error: Unsupported left operand type for + ("U")
Found 1 error in 1 file (checked 1 source file)

Replacing __iadd__ with __add__ makes mypy happy (so clearly mypy can infer required operation and fall back with +).

# main.py

from __future__ import annotations
from typing import Generic, Protocol, TypeVar     


T = TypeVar("T")
U = TypeVar("U", bound=SupportsIAdd)


class SupportsIAdd(Protocol):
    def __add__(self: T, other: T) -> T: ... 


class AddingAccumulatorParam(Generic[U]):
    def addInPlace(self, value1: U, value2: U) -> U:
        value1 += value2  # Passes just fine
        return value1

On Further investigation, it seems like the issue might be related to bound TypeVar ‒ if I replace Protocol

class SupportsIAdd(Protocol):

with plain class

class SupportsIAdd:

the problem still exists, but if I tweak the signature of the method that uses it, to take exact class

# main.py

from __future__ import annotations
from typing import Any, TypeVar              


class SupportsIAdd:          
    def __iadd__(self, other: Any) -> SupportsIAdd: ...         


class AddingAccumulatorParam:            
    def addInPlace(self, value1: SupportsIAdd, value2: SupportsIAdd) -> SupportsIAdd:            
        value1 += value2
        return value1

it passes.

Finally (credit goes to @hauntsaninja) applying __iadd__ directly

class AddingAccumulatorParam(Generic[U]):
    def addInPlace(self, value1: U, value2: U) -> U:
        return reveal_type(value1.__iadd__(value2))

yields expected result

main.py:17: note: Revealed type is "U`1"

Your Environment

  • Mypy version used: Both 0.910 and dev.
  • Mypy command-line flags: Default flags.
  • Mypy configuration options from mypy.ini (and other config files): No additional config used.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne damit, die Beispiele in main.py mit mypy unter Verwendung der Standard-Flags zu reproduzieren und += mit direkten Aufrufen von iadd und add zu vergleichen. Füge eine Regressionstestabdeckung für einen gebundenen TypeVar mit einem Protocol iadd hinzu; fertig ist die Arbeit, wenn das Beispiel ohne den Fehler unsupported-left-operand durchläuft.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
devtools
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
35/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.