python / python/mypy

regression with Generic/ParamSpec between 1.4.1 and 1.5.0

Aperta
#15,844 6 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

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

Descrizione

Bug Report

With 1.5.0, some of our software has started to surface a var-annotated ("Need type annotation for") error that it did not surface with 1.4.1; I could not find anything in the 1.5.0 release notes that suggested that this was intentional.

To Reproduce

Our actual software is a bit domain-specific, but I've tried to extract the details; the concept involves a data descriptor that attaches a callable to a class and generates a new callable that inverts how the callable's constructor and runtime arguments are supplied. The solution depends on ParamSpec to preserve the argument signatures and uses Generic so that the same internals can be used for different functions.

#!/usr/bin/env python3
from dataclasses import dataclass
from typing import Generic, ParamSpec, Protocol, Self, TypeVar, overload


P = ParamSpec("P")
T = TypeVar("T")


class Transform(Protocol[P, T]):
    """Generic protocol to transform a value using inputs."""

    def __init__(self, *args: P.args, **kwargs: P.kwargs) -> None:
        raise NotImplementedError

    def __call__(self, value: T) -> T:
        raise NotImplementedError


class BoundTransform(Generic[P, T]):
    """Wrapper around a transform function that performs late-binding."""

    def __init__(
        self,
        value: T,
        transform_cls: type[Transform[P, T]],
    ) -> None:
        self.value: T = value
        self.transform_cls: type[Transform[P, T]] = transform_cls

    def __call__(
        self,
        *args: P.args,
        **kwargs: P.kwargs,
    ) -> T:
        return self.transform_cls(*args, **kwargs)(self.value)


class HasValue(Protocol[T]):
    value: T

class Transformer(Generic[P, T]):
    """Data descriptor that applies the bound transformer to something with a value."""

    def __init__(
        self,
        transform_cls: type[Transform[P, T]],
    ) -> None:
        self.transform_cls: type[Transform[P, T]] = transform_cls

    @overload
    def __get__(self, obj: None, objtype: type[HasValue[T]]) -> Self:
        ...

    @overload
    def __get__(self, obj: HasValue[T], objtype: type[HasValue[T]]) -> BoundTransform[P, T]:
        ...

    def __get__(self, obj: HasValue[T] | None, objtype: type[HasValue[T]]) -> Self | BoundTransform[P, T]:
        if obj is None:
            return self

        return BoundTransform(obj.value, self.transform_cls)


@dataclass(frozen=True)
class Add:
    """Transform implementation that adds two integers."""
    value: int

    def __call__(self, value: int) -> int:
        return value + self.value


@dataclass(frozen=True)
class HasNumber(HasValue[int]):
    """HasValue implementation that holds an integer."""
    value: int

    add = Transformer(Add)



if __name__ == "__main__":
    # Usage example
    foo = HasNumber(42)
    bar = foo.add(-42)
    assert bar == 0

Expected Behavior

No errors from mypy. This is true for 1.4.1.

Actual Behavior

Errors. This is true for 1.5.0

main.py:81: error: Need type annotation for "add"  [var-annotated]
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: 1.4.1 and 1.5.0
  • Mypy command-line flags: n/a
  • Mypy configuration options from mypy.ini (and other config files): n/a
  • Python version used: 3.11.4

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

Usa la riproduzione main.py fornita ed eseguila con mypy 1.4.1 e 1.5.0, concentrandoti sull’assegnazione add alla riga 81 e sulla relativa diagnostica var-annotated. Confronta il comportamento del descrittore Generic/ParamSpec tra le versioni; il lavoro è completato quando l’errore imprevisto è risolto e la regressione è coperta da un test appropriato.

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

Valutazione

Stack tecnologico
python
Ambito
tooling
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.