python / python/mypy

Return types for Overloads with "Optional[TypeVar]" arguments are deducted as just "object"

Aperta
#16,591 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

Please see the following mypy playground: https://mypy-play.net/?mypy=latest&python=3.12&gist=207fe333eb8e226d36b84bf34be5ca65

I personally encountered this on mypy 1.7.1 and Python 3.8, but the mypy playground link uses the latest version of everything.

Consider the following helper function:

import datetime
from typing import Any, Optional, Sized, Tuple, Type, TypeVar, Union

from typing_extensions import Literal, overload, TypeAlias

_T = TypeVar("_T")


class TypeGuardError(Exception):
    pass


_THasLen = TypeVar("_THasLen", bound=Sized)


@overload
def required(
    value: Optional[_T],
    *,
    message: Optional[str] = None,
    property_name: Optional[str] = None,
) -> _T:
    pass


@overload
def required(
    value: Any,
    *,
    instance_of: Type[_T],
    instance_of_message: Optional[str] = None,
    message: Optional[str] = None,
    property_name: Optional[str] = None,
) -> _T:
    pass


def required(
    value: Optional[Any],
    *,
    instance_of: Optional[Any] = None,
    message: Optional[str] = None,
    property_name: Optional[str] = None,
    instance_of_message: Optional[str] = None,
) -> Any:
    if property_name is not None:
        message_prefix = f"{property_name}: "
    else:
        message_prefix = ""

    if value is None:
        raise TypeGuardError(message or f"{message_prefix}Value cannot be `None`.")

    if instance_of and not isinstance(value, instance_of):
        raise TypeGuardError(
            instance_of_message
            or message
            or f'{message_prefix}Expected value of type "{instance_of.__name__}", got value of type "{type(value).__name__}" instead.'
        )

    return value

required here just checks if the value is not None, optionally verifying its type as well. If any validation fails, it's able to generate an error, with either a message you define, or a parameterizable auto-generated one.

The returned value is just the input value, but cast to its "pure type" through overloading (removing None from the type by type-matching it to Optional). But when passing the instance_of argument, the return type is whatever type you pass to that argument instead. That's the intention.

However, mypy fails my expectations. For example:

TimeDeltaLike: TypeAlias = Union[int, float, str, datetime.timedelta]

default_lock_time: Optional[TimeDeltaLike] = None

locked_for = required(
    default_lock_time,
    message=f"Entity does not specify a default lock time.",
)

# QUESTION: Why is the revealed type `object`?
reveal_type(locked_for)

In the last line, the revealed type is object.

When analyzing the same code through Pyright, the revealed type is int | float | str | datetime.timedelta, as I would expect.

To Reproduce

# Ideally, a small sample program that demonstrates the problem.
# Or even better, a reproducible playground link https://mypy-play.net/ (use the "Gist" button)

See explanation above.

Expected Behavior

See explanation above.

Actual Behavior

See explanation above.

Your Environment

  • Mypy version used: 1.7.1
  • Mypy command-line flags:
  • Mypy configuration options from mypy.ini (and other config files):
  • Python version used: 3.8

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 con il mypy playground collegato e gli overload required che usano Optional[TypeVar]; esamina come viene inferita la chiamata con default_lock_time prima di reveal_type. Il lavoro è completato quando il tipo restituito rivelato è int | float | str | datetime.timedelta invece di object, preservando il comportamento degli overload instance_of.

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
42/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.