Skip to content
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

TypeVar bound to Never when mypy can't infer the appropriate value #18154

Open
fofoni opened this issue Nov 14, 2024 · 2 comments
Open

TypeVar bound to Never when mypy can't infer the appropriate value #18154

fofoni opened this issue Nov 14, 2024 · 2 comments
Labels
bug mypy got something wrong topic-type-variables

Comments

@fofoni
Copy link

fofoni commented Nov 14, 2024

In the second call to decorator_factory() with no arguments, _T should be bound to int. I understand that inferring _T=int is maybe too much for mypy, but I believe _T should then be bound to Any, or maybe object to be extra strict, but definitely not Never, which is what currently happens.

https://mypy-play.net/?mypy=master&python=3.13&gist=4a1aeecb04582945c4fa78f196bace5f

from collections.abc import Callable
from typing import Any, Generic, ParamSpec, TypeVar


_T = TypeVar("_T", covariant=True)
_P = ParamSpec("_P")

class Box(Generic[_T]): ...

def decorator_factory(box_type: type[Box[_T]] = Box) -> Callable[
    [Callable[_P, _T]],
    Callable[_P, _T]
]:
    # Implementation doesn't matter
    x: Any
    return x


class Class:
    @decorator_factory(box_type=Box[int])
    def method_one(self) -> int:
        return 0

    @decorator_factory()
    def method_two(self) -> int:
        return 0

For the record, pyright has no complaints about this code.

@fofoni fofoni added the bug mypy got something wrong label Nov 14, 2024
@brianschubert
Copy link
Collaborator

As a workaround, you can try giving _T a default type argument. For example:

_T = TypeVar("_T", covariant=True, default=int)

reveal_type(decorator_factory())   # N: "def [_P] (def (*_P.args, **_P.kwargs) -> builtins.int) -> def (*_P.args, **_P.kwargs) -> builtins.int"
reveal_type(Class.method_one)      # N: "def (self: SCRATCH.Class) -> builtins.int"
reveal_type(Class().method_one())  # N: "builtins.int"

Using default=Any or default=object would also work, depending on what makes the most sense in your application.

For the record, while pyright doesn't complain, it's just as confused as mypy about how to bind _T, just silently so 😉:

# pyright 1.1.389
reveal_type(decorator_factory())   # Type of "decorator_factory()" is "((**_P@decorator_factory) -> Unknown) -> ((**_P@decorator_factory) -> Unknown)"
reveal_type(Class.method_two)      # Type of "Class.method_two" is "(self: Class) -> Unknown"
reveal_type(Class().method_two())  # Type of "Class().method_two()" is "Unknown"

@erictraut
Copy link

while pyright doesn't complain, it's just as confused as mypy

Pyright is not confused here; it's working correctly. It binds the TypeVar based on the default value Box which is equivalent to Box[Unknown]. (Pyright uses Unknown to refer to an implied Any.)

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-variables
Projects
None yet
Development

No branches or pull requests

3 participants