python / python/mypy

Collapsed Optionals in TypeVar cause weird error messages

Aperta
#8,478 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

bug false-positive priority-1-normal topic-type-variables
Lingua principale
Python
Stelle
20.6k
Fork
3.3k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

At a minimum, I'm reporting a bug in an error message.

I have a function, run_tasks, which takes a list of tasks and a worker function, and executes the worker function on almost all the tasks. For some of them, run_tasks will decide to not execute the task.

run_tasks returns List[Optional[Result]] where Result is a TypeVar.

This works until the worker function's returns Optional[x] where Result is now Optional[x] and run_tasks is asked to return List[Optional[Optional[x]]].

Example code:

from typing import (
    TypeVar,
    List,
    Callable,
    Optional,
)

Task = TypeVar("Task")
Result = TypeVar("Result")


def run_tasks(
    tasks: List[Task], worker_fun: Callable[[Task], Result]
) -> List[Optional[Result]]:
    results: List[Optional[Result]] = []

    toggle: bool = True
    todo: Task
    for todo in tasks:
        toggle = not toggle

        if toggle:
            work_result: Result
            work_result = worker_fun(todo)
            results.append(work_result)
        else:
            results.append(None)

    return results


def test() -> None:
    def worker(d: int) -> Optional[int]:
        if d % 2 == 0:
            return None
        else:
            return d + 1

    output: List[Optional[Optional[int]]] = run_tasks(
        [1, 2, 2, 1], worker,
    )
    from pprint import pprint

    pprint(output)
    assert output == [None, None, None, 2]


test()

I get the error message: error: Argument 2 to "run_tasks" has incompatible type "Callable[[int], Optional[int]]"; expected "Callable[[int], int]"

I understand Python can't really represent Optional[Optional[int]]. The problem is in the error message, I think. This code does work correctly, and validates:

def test2() -> None:
    def worker(d: int) -> int:
        return d + 1

    output: List[Optional[int]] = run_tasks(
        [1, 2, 2, 1], worker,
    )
    from pprint import pprint

    pprint(output)
    assert output == [None, 3, None, 2]


test2()

and so someone unfamiliar with the intricacies and details may be confused by the type signature being correctly updated in 2 places, but not validating.

Instead, I sort of expected it to either (a) work, or (b) have an error specific to the case of nested Optional.

$ mypy --version
mypy 0.761

$ python --version
Python 3.7.6

flags: none.

My apologies, but I did not test with mypy from Git master.

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 il riproduttore run_tasks fornito con mypy e confronta la diagnosi di nested-Optional con il caso funzionante test2. Il lavoro è completato quando l'errore di Callable incompatibile è stato corretto o sostituito con una diagnosi che spiega accuratamente questo caso di nested Optional, con copertura di regressione per l'esempio.

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.