Content-Length: 252846 | pFad | http://github.com/python/mypy/issues/18816

4F mypy failed to evaluate TypeVar in function parameter · Issue #18816 · python/mypy · GitHub
Skip to content

mypy failed to evaluate TypeVar in function parameter #18816

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
KaitoHH opened this issue Mar 19, 2025 · 2 comments
Closed

mypy failed to evaluate TypeVar in function parameter #18816

KaitoHH opened this issue Mar 19, 2025 · 2 comments
Labels
bug mypy got something wrong topic-type-context Type context / bidirectional inference topic-type-variables

Comments

@KaitoHH
Copy link

KaitoHH commented Mar 19, 2025

Bug Report

mypy cannot infer typevar which is defined as the function parameter, even though it could be evaluated from the following decorated function

To Reproduce

import functools
import time
from typing import Callable, ParamSpec, TypeVar, Any

_P = ParamSpec("_P")
_T = TypeVar("_T")


def retry(
    max_retry: int = 3,
    interval: int = 1,
    should_retry: Callable[[_T], bool] | None = None,
) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]:
    def decorator(func: Callable[_P, _T]) -> Callable[_P, _T]:
        @functools.wraps(func)
        def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
            failed_cnt = 0
            while True:
                try:
                    result = func(*args, **kwargs)
                    if should_retry is not None and should_retry(result):
                        raise ValueError("result is not expected")
                    return result
                except Exception as e:
                    failed_cnt += 1
                    if failed_cnt > max_retry:
                        e.add_note(f"Retry {max_retry} times failed.")
                        raise e
                    time.sleep(interval)

        return wrapper

    return decorator

@retry() # Argument 1 has incompatible type "Callable[[int], int]"; expected "Callable[[int], Never]"
def f(x: int) -> int:
    return x


if __name__ == "__main__":
    f(1)

Expected Behavior

no error

Actual Behavior

Argument 1 has incompatible type "Callable[[int], int]"; expected "Callable[[int], Never]"

Your Environment

  • Mypy version used: 1.15.0
  • Python version used: 3.12
@KaitoHH KaitoHH added the bug mypy got something wrong label Mar 19, 2025
@sterliakov
Copy link
Collaborator

The high-level problem is that _T is bound at the first call site (retry()) if should_retry passed, but only at the application site (retry()(...)) otherwise. Since _T is clearly present in args of your function, mypy always picks the first way.

I'm not even sure this is a bug. Pyright also solves _T to Unknown in your snippet.

As a workaround, you can define overloaded signatures, so that retry() without should_retry does not contain _T in the arguments types: https://mypy-play.net/?mypy=master&python=3.12&flags=strict&gist=1fecf44b1a447f66c51de6ac7276e013

@overload
def retry(
    max_retry: int = 3,
    interval: int = 1,
    should_retry: None = None,
) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]: ...
@overload
def retry(
    max_retry: int = 3,
    interval: int = 1,
    should_retry: Callable[[_T], bool] = ...
) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]: ...
def retry(
    max_retry: int = 3,
    interval: int = 1,
    should_retry: Callable[[_T], bool] | None = None,
) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]:
    ...

@sterliakov sterliakov added topic-type-variables topic-type-context Type context / bidirectional inference labels Mar 19, 2025
@KaitoHH
Copy link
Author

KaitoHH commented Mar 22, 2025

The high-level problem is that _T is bound at the first call site (retry()) if should_retry passed, but only at the application site (retry()(...)) otherwise. Since _T is clearly present in args of your function, mypy always picks the first way.

I'm not even sure this is a bug. Pyright also solves _T to Unknown in your snippet.

As a workaround, you can define overloaded signatures, so that retry() without should_retry does not contain _T in the arguments types: https://mypy-play.net/?mypy=master&python=3.12&flags=strict&gist=1fecf44b1a447f66c51de6ac7276e013

@overload
def retry(
    max_retry: int = 3,
    interval: int = 1,
    should_retry: None = None,
) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]: ...
@overload
def retry(
    max_retry: int = 3,
    interval: int = 1,
    should_retry: Callable[[_T], bool] = ...
) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]: ...
def retry(
    max_retry: int = 3,
    interval: int = 1,
    should_retry: Callable[[_T], bool] | None = None,
) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]:
    ...

Thank you so much for the solution. Actually I had previously suspected that without should_retry args, if we only defined foo=retry(), the type of foo remains unknown, which causes the complaint of mypy, so I guessed that causes the problem.

Anyway thanks for the reply.

@KaitoHH KaitoHH closed this as completed May 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-type-context Type context / bidirectional inference topic-type-variables
Projects
None yet
Development

No branches or pull requests

2 participants








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/python/mypy/issues/18816

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy