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

Regression: ParamSpecs are converted to '...' when used in returned Callable #5283

Closed
GugelRobin opened this issue Jun 13, 2023 · 8 comments
Closed
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working

Comments

@GugelRobin
Copy link

GugelRobin commented Jun 13, 2023

Describe the bug
See code.

Pyright >1.1.309 starts having the bug

Pylance has the problem as well.

Code or Screenshots

from typing import TypeVar, ParamSpec

T = TypeVar("T")
P = ParamSpec("P")

def foo() -> Callable[[Callable[P, T]], Callable[P, T]]:
    ...

def bar(x: int) -> str:
    ...

reveal_type(foo()(bar)) 
# pyright >  1.1.310 outputs: Type of "foo()(bar)" is "(...) -> str"
# pyright <= 1.1.309 outputs: Type of "foo()(bar)" is "(x: int) -> str"
@GugelRobin GugelRobin added the bug Something isn't working label Jun 13, 2023
@erictraut
Copy link
Collaborator

This is an ambiguous part of the typing spec. In a strict reading of PEP 484, both P and T are scoped to foo and must be solved in the context of a call to foo. However, pyright and mypy provide some special-case logic when a type variable is used only within a return callable where they assume that the type variable is scoped to the callable rather than the function. It looks like I broke that special casing for ParamSpec but not for regular type variables. I'll investigate.

erictraut pushed a commit that referenced this issue Jun 13, 2023
…tion that returns a callable with a ParamSpec that does not appear outside of the return type annotation. This addresses #5283.
erictraut added a commit that referenced this issue Jun 13, 2023
…tion that returns a callable with a ParamSpec that does not appear outside of the return type annotation. This addresses #5283. (#5284)

Co-authored-by: Eric Traut <[email protected]>
@erictraut
Copy link
Collaborator

This will be fixed in the next release.

@erictraut erictraut added the addressed in next version Issue is fixed and will appear in next published version label Jun 13, 2023
@erictraut
Copy link
Collaborator

This is addressed in pyright 1.1.314, which I just published. It will also be included in this week's Pylance prerelease build.

@rslinckx
Copy link

@erictraut I have the following test case which seems to also have ceased working after 1.1.309, and seems related:

from __future__ import annotations

from dataclasses import dataclass
from typing import Callable, Generic, ParamSpec

P = ParamSpec("P")


@dataclass
class TaskDeclaration(Generic[P]):
    func: Callable[P, None]

    def enqueue(self, something: str, *args: P.args, **kwargs: P.kwargs) -> None:
        pass


def task() -> Callable[[Callable[P, None]], TaskDeclaration[P]]:
    def decorator(func: Callable[P, None]) -> TaskDeclaration[P]:
        return TaskDeclaration(func)

    return decorator


@task()
def test(a: int) -> None:
    pass


test.enqueue("something") # Should have been test.enqueue("something", 3) to be valid

Output with <=1.1.309: test.py:29:1 - error: Argument missing for parameter "a" (reportGeneralTypeIssues)
Output with >1.1.309(including 314): 0 errors, 0 warnings, 0 informations

@GugelRobin
Copy link
Author

GugelRobin commented Jun 15, 2023

@erictraut @rslinckx
The problem seems to be that the ParamSpec is still bound in the returned callable when the ParamSpec is used in a generic class (while a TypeVar still stays unbound).
Strange enough, if P is bound just by generic classes (and not a callable) it still works.
I've produced a minimal example for you (pyright 1.1.314)

from typing import Callable, Generic, ParamSpec

P = ParamSpec("P")
T = TypeVar("T")

class Foo(Generic[P, T]):
    ...

class Bar(Generic[P, T]):
    ...

def fail() -> Callable[[Callable[P, T]], Foo[P, T]]:
    ...

def success() -> Callable[[Callable[P, T]], Callable[P, T]]:
    ...

def success2() -> Callable[[Foo[P, T]], Bar[P, T]]:
    ...

reveal_type(fail())
# Type of "fail()" is "((**P@fail) -> T@fail) -> Foo[(...), T@fail]"
reveal_type(success())
# Type of "success()" is "((**P@success) -> T@success) -> ((**P@success) -> T@success)"
reveal_type(success2())
# Type of "success2()" is "(Foo[P@success2, T@success2]) -> Bar[P@success2, T@success2]"

@erictraut erictraut reopened this Jun 15, 2023
@erictraut erictraut removed the addressed in next version Issue is fixed and will appear in next published version label Jun 15, 2023
erictraut pushed a commit that referenced this issue Jun 15, 2023
…tion that returns a callable with a ParamSpec that does not appear outside of the return type annotation. A previous fix didn't correctly handle the case that involved a class parameterized with a ParamSpec. This addresses #5283.
@erictraut
Copy link
Collaborator

Ah, my bad. I missed a small detail that caused my previous fix not to work with a class parameterized with a ParamSpec, as with TaskDeclaration in your example above. I've fixed the issue and further augmented the pyright unit tests to cover this case.

@erictraut erictraut added the addressed in next version Issue is fixed and will appear in next published version label Jun 15, 2023
@Hootner

This comment was marked as spam.

@erictraut
Copy link
Collaborator

This is included in pyright 1.1.315, which I just published. It will also be included in this week's insiders release of pylance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants