python / python/mypy

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

Aperta
#11,745 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

bug topic-protocols topic-type-variables
Lingua principale
Python
Stelle
20.6k
Fork
3.3k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

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.

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia riproducendo gli esempi in main.py con mypy usando i flag predefiniti e confrontando += con chiamate dirette a iadd e add. Aggiungi una copertura di regressione per un TypeVar vincolato con un Protocol iadd; il lavoro sarà completato quando l’esempio passerà senza l’errore unsupported-left-operand.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
python
Ambito
devtools
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.