python / python/mypy

Incorrect type inference with generic decorator type

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

Dieses Issue hat noch niemand übernommen.

bug
Vorherrschende Sprache
Python
Sterne
20.6k
Forks
3.3k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

Bug Report

I'm trying to define a generic type alias for a return-type-changing decorator:

T = TypeVar("T")
R = TypeVar("R")
Decorator = Callable[[Callable[..., T]], Callable[..., R]]

So for example, if a decorator wraps a return value in a list, its type could be Decorator[T, List[T]], and it's much clearer than nested Callables.

However, this doesn't work. Here's an example (also see on mypy-play):

from typing import *

T = TypeVar("T")
R = TypeVar("R")
Decorator = Callable[[Callable[..., T]], Callable[..., R]]

# def to_list() -> Decorator[T, List[T]]:
def to_list() -> Callable[[Callable[..., T]], Callable[..., List[T]]]:
    def decorator(func):
        def wrapped(*args, **kwargs):
            return [func(*args, **kwargs)]
        return wrapped
    return decorator
reveal_type(to_list)
reveal_type(to_list())

@to_list()
def foo() -> int:
    return 1

assert foo() == [1]
reveal_type(foo())


def to_list2() -> Decorator[T, List[T]]: ...
reveal_type(to_list2)
reveal_type(to_list2())

@to_list2()
def foo2() -> int:
    return 1
    
assert foo() == [1]
reveal_type(foo2())

Expected Behavior

All things type-check and foo has the same type as foo2.

Actual Behavior

main.py:14: note: Revealed type is "def () -> def [T] (def (*Any, **Any) -> T`-1) -> def (*Any, **Any) -> builtins.list[T`-1]"
main.py:15: note: Revealed type is "def [T] (def (*Any, **Any) -> T`-1) -> def (*Any, **Any) -> builtins.list[T`-1]"
main.py:22: note: Revealed type is "builtins.list[builtins.int*]"
main.py:26: note: Revealed type is "def [T] () -> def (def (*Any, **Any) -> T`-1) -> def (*Any, **Any) -> builtins.list[T`-1]"
main.py:27: note: Revealed type is "def (def (*Any, **Any) -> <nothing>) -> def (*Any, **Any) -> builtins.list[<nothing>]"
main.py:29: error: Argument 1 has incompatible type "Callable[[], int]"; expected "Callable[..., <nothing>]"
main.py:34: note: Revealed type is "builtins.list[<nothing>]"
Found 1 error in 1 file (checked 1 source file)

Mypy inferred foo correctly but not foo2. Somehow <nothing> got in where there should be TypeVars. The signatures for to_list and to_list2 are also different and I don't really understand why.

I'm guessing it's because I'm substituting two different TypeVars (T and R) with type expressions that reference the same TypeVar T? But I can't really find a formal definition of how TypeVars work, so I'm not sure if this is supported or not.

Your Environment

  • Mypy version used: latest
  • Mypy command-line flags: default
  • Python version used: 3.10
  • Operating system and version: mypy-play

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 bereitgestellte mypy-play-Reproduktion mit der gemeldeten Python 3.10-Umgebung auszuführen, und vergleiche die angezeigten Typen und den Fehler mit dem erwarteten Verhalten. Verfolge die beteiligte generische Typinferenz des Decorators in Decorator, to_list und to_list2; abgeschlossen ist die Aufgabe, wenn beide Formen eine Typprüfung bestehen und foo und foo2 äquivalente Listentypen als Rückgabetyp erhalten.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

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

Neue Issues direkt in Ihr Postfach

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