Skip to content
New issue

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

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

Already on GitHub? # to your account

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

Closed
fofoni opened this issue Nov 14, 2024 · 4 comments
Closed

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

fofoni opened this issue Nov 14, 2024 · 4 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.)

@sterliakov
Copy link
Contributor

It's essentially #3737 - consider using a pair of overloads with and without the argument. Never is a perfectly correct inferred type to represent "inference failed" IMO

@fofoni
Copy link
Author

fofoni commented Nov 23, 2024

Closing because I agree it's essentially the same issue.

@fofoni fofoni closed this as completed Nov 23, 2024
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
bug mypy got something wrong topic-type-variables
Projects
None yet
Development

No branches or pull requests

4 participants