python / python/mypy

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

オープン
#16,591 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

bug
主要言語
Python
スター
20.6k
フォーク
3.3k
PR マージ指標
PR 指標を取得中

説明

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

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

リンク先の mypy playground と Optional[TypeVar] を使用する required オーバーロードから始め、reveal_type の前に default_lock_time を指定した呼び出しがどのように推論されるかを調べます。instance_of オーバーロードの動作を維持したまま、表示される戻り値の型が object ではなく int | float | str | datetime.timedelta になれば作業完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
python
領域
devtools
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
42/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。