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

Using ParamSpec in decorator with new type inference results in Never populated as type arguments #16512

Closed
flaeppe opened this issue Nov 17, 2023 · 1 comment · Fixed by #16514
Labels
bug mypy got something wrong

Comments

@flaeppe
Copy link
Contributor

flaeppe commented Nov 17, 2023

Bug Report

Using a decorator including a ParamSpec variable on a function declaring multiple parameters from a generic protocol swaps any protocol argument to Never (pheuh, having a bit of trouble phrasing this decently..)

To Reproduce

I drilled down an issue in our code base, when running with 1.7.0, to the following script. This passes fine with 1.6.1. It might probably be reduced further.

While I was at it I ran git bisect with the script below and ended up at 93d4cb0 (#16345) (CC @ilevkivskyi).

I see that there's a flag to run with the old type inference to resolve things for now.

Playground link

from collections.abc import Callable
from typing import Concatenate, ParamSpec, Protocol, TypeVar

T = TypeVar("T")
P = ParamSpec("P")
V_co = TypeVar("V_co", covariant=True)


class Metric(Protocol[V_co]):
    def __call__(self) -> V_co:
        ...


def simple_metric(func: Callable[Concatenate[int, P], T]) -> Callable[P, T]:
    def inner(*args: P.args, **kwargs: P.kwargs) -> T:
        return func(*args, **kwargs)

    return inner


@simple_metric
def Negate(count: int, /, metric: Metric[float]) -> float:
    return metric() * -1


@simple_metric
def Combine(count: int, m1: Metric[T], m2: Metric[T], /, *more: Metric[T]) -> T:
    return m1()


reveal_type(simple_metric)
reveal_type(Negate)
reveal_type(Combine)

def m1() -> float:
    return 0.0
    
def m2() -> float:
    return 1.0

reveal_type(Combine(m1, m2))

Expected Behavior

There should be no Never populated as generic argument and script should pass.

Actual Behavior

Some strategic reveals in the script but the 2 errors it spits back are:

main.py:41: error: Argument 1 to "Combine" has incompatible type "Callable[[], float]"; expected "Metric[Never]"  [arg-type]
main.py:41: error: Argument 2 to "Combine" has incompatible type "Callable[[], float]"; expected "Metric[Never]"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)

What's interesting is that if I only keep 1 Metric[T] argument e.g.

@simple_metric
def Combine(count: int, m1: Metric[T], /) -> T:
    return m1()

It seems to get things right.

Your Environment

  • Mypy version used: 1.7.0 (also master)
  • Mypy command-line flags: -
  • Mypy configuration options from mypy.ini (and other config files): -
  • Python version used: Python3.11
@flaeppe flaeppe added the bug mypy got something wrong label Nov 17, 2023
@kjagiello
Copy link
Sponsor

kjagiello commented Nov 17, 2023

Tried reducing the repro case:

from collections.abc import Callable
from typing import Concatenate, ParamSpec, Protocol, TypeVar

T = TypeVar("T")
P = ParamSpec("P")
V_co = TypeVar("V_co", covariant=True)

class SomeGeneric(Protocol[V_co]):
    def __call__(self) -> V_co: ...
    
def decorator(func: Callable[P, T]) -> Callable[P, T]: ...

@decorator
def f1(x1: SomeGeneric[T], x2: SomeGeneric[T]) -> T: ...
def f2() -> float: ...

reveal_type(decorator)
reveal_type(f1)
reveal_type(f1(f2, f2))

Playground link

JukkaL pushed a commit that referenced this issue Nov 22, 2023
Fixes #16512

The problems were caused if same callback protocol appeared multiple
times in a signature. Previous logic confused this with a recursive
callback protocol.
JukkaL pushed a commit that referenced this issue Nov 22, 2023
Fixes #16512

The problems were caused if same callback protocol appeared multiple
times in a signature. Previous logic confused this with a recursive
callback protocol.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants