python / python/mypy

Incorrect type inference with generic decorator type

Aperta
#11,369 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

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

Descrizione

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

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 eseguendo la riproduzione mypy-play fornita con l’ambiente Python 3.10 indicato e confronta i tipi rivelati e l’errore con il comportamento previsto. Traccia l’inferenza dei tipi del decoratore generico coinvolta in Decorator, to_list e to_list2; il lavoro è completato quando entrambe le forme superano il controllo dei tipi e foo e foo2 ricevono tipi equivalenti che restituiscono liste.

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

Valutazione

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

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.