python / python/mypy

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

Abierto
#11,745 2 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

bug topic-protocols topic-type-variables
Lenguaje dominante
Python
Estrellas
20.6k
Forks
3.3k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

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.

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Comienza reproduciendo los ejemplos de main.py con mypy usando los flags predeterminados y comparando += con llamadas directas a iadd y add. Añade cobertura de regresión para un TypeVar acotado con un Protocol iadd; estará terminado cuando el ejemplo pase sin el error unsupported-left-operand.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
python
Área
devtools
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Estancado
Claridad
Bastante claro
Aptitud para principiantes
35/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.