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

Decorator factory function typing #15037

Closed
ssamojl opened this issue Apr 11, 2023 · 7 comments · Fixed by #15272
Closed

Decorator factory function typing #15037

ssamojl opened this issue Apr 11, 2023 · 7 comments · Fixed by #15272
Labels
bug mypy got something wrong topic-paramspec PEP 612, ParamSpec, Concatenate

Comments

@ssamojl
Copy link

ssamojl commented Apr 11, 2023

Bug Report

I have a decorator factory function that takes a schema and returns a decorator that validates something against the provided schema and also passes in an argument to the wrapped function. Here's a simplified version of the code:

from typing import Any, Callable, Concatenate, ParamSpec, TypeVar

P = ParamSpec("P")
Schema = TypeVar("Schema") 
Ret = TypeVar("Ret") 

def validate(
    schema: Schema,
) -> Callable[
    [Callable[Concatenate[int, P], Ret]],
    Callable[P, Ret],
]:
    def decorator(
        f: Callable[
            Concatenate[int, P], Ret
        ]
    ) -> Callable[P, Ret]:
        def wrap(*args: P.args, **kwargs: P.kwargs) -> Ret: 
            # for simplicity, omitting schema validation 
            # that would normally happen here.
            
            additional_arg = 42 # also simplified
            return f(additional_arg, *args, **kwargs)

        return wrap

    return decorator

With mypy 1.1.1, the type annotations are fine. But with the recently released mypy 1.2.0, I get this error:

error: Incompatible return value type (got "Callable[[Arg(Callable[[int, **P], Ret], 'f')], Callable[P, Ret]]", expected "Callable[[Callable[[int, **P], Ret]], Callable[P, Ret]]")  [return-value]

To Reproduce

Run the following with mypy 1.2.0 (example returns an error) and mypy 1.1.1 (no error):

Expected Behavior

I'd expect mypy to not return an error unless there is a legitimate issue with the types in this code (if that's the case, please let me know!).

Moreover, the actual type in the error message doesn't seem to be a valid type (specifically Arg(Callable[[int, **P], Ret], 'f') in this example).

Actual Behavior

error: Incompatible return value type (got "Callable[[Arg(Callable[[int, **P], Ret], 'f')], Callable[P, Ret]]", expected "Callable[[Callable[[int, **P], Ret]], Callable[P, Ret]]") [return-value]

Your Environment

  • Mypy version used: 1.2.0
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): n/a
  • Python version used: 3.11
@ssamojl ssamojl added the bug mypy got something wrong label Apr 11, 2023
@dgoldstein0
Copy link

suggestion: I've had better luck with Protocol where I give a __call__ signature, than with Callable. doesn't necessarily address your issue but may give you a way to write the code that works better with mypy.

@hauntsaninja
Copy link
Collaborator

Thanks for the report!

From a glance, it seems like the type variable scoping isn't aligned with what you're expecting. E.g. in the following version you get the fun error Incompatible return value type (got "Callable[[Callable[[int, **P], Ret]], Callable[P, Ret]]", expected "Callable[[Callable[[int, **P], Ret]], Callable[P, Ret]]"):

from typing import Any, Callable, Concatenate, ParamSpec, TypeVar

P = ParamSpec("P")
Ret = TypeVar("Ret") 

def validate() -> Callable[
    [Callable[Concatenate[int, P], Ret]],
    Callable[P, Ret],
]:
    def decorator(f: Callable[Concatenate[int, P], Ret], /) -> Callable[P, Ret]:
        def wrap(*args: P.args, **kwargs: P.kwargs) -> Ret: 
            return f(42, *args, **kwargs)
        return wrap

    return decorator

The difference between 1.1.1 and 1.2 bisects to #14677 , but it's also very possible that 1.1.1 wasn't handling things correctly, didn't look to see.

cc @A5rocks the paramspec fixer, in case they have thoughts!

@gandhis1
Copy link

gandhis1 commented Apr 13, 2023

I'm seeing the same issue, a new (false?) negative in Mypy 1.20 that was not present in 1.1:

error: Incompatible return value type (got "Callable[[Arg(Callable[[Table, **P], Executable], 'func')], Callable[P, Result]]", expected "Callable[[Callable[[Table, **P], Executable]], Callable[P, Result]]") [return-value]

I've replaced some of the unneeded parts here with "..." for brevity:

def second_order_decorator(...) -> Callable[[Callable[Concatenate[sa.Table, P], sa.sql.Executable]], Callable[P, sa.engine.Result]]:
    def first_order_decorator(func: Callable[Concatenate[sa.Table, P], sa.sql.Executable]) -> Callable[P, sa.engine.Result]:
        @wraps(func)
        def wrapped_func(*args: P.args, **kwargs: P.kwargs) -> sa.engine.Result:
            return ...   # for brevity

        return wrapped_func

    return first_order_decorator

@A5rocks
Copy link
Contributor

A5rocks commented Apr 14, 2023

I was preparing to have to dig into the codebase to figure out what went wrong but I just realized what might have happened: I may have somehow leaked strict-mode concatenate handling into non-strict-mode.

While typing that up it turns out my proposed solution actually doesn't fix it. Heh. IIRC this issue is what I mentioned in #14677 (comment) which gets fixed by my followup #14903 (at least the error gets removed) which I really need to get around to finalizing! In fact now that I check, this specific error pops up in the test case I added when checked against current mypy!

@abid-mujtaba
Copy link

Looks like the fix was reverted: #15272 (comment). Can we please re-open this issue so that we can keep track of the problem? Thanks.

@A5rocks
Copy link
Contributor

A5rocks commented Aug 10, 2023

FYI, #15837 also fixes this, so IDK if you want to reopen just for tracking that or what.

@abid-mujtaba
Copy link

Subscribing to the PR should suffice, thanks.

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-paramspec PEP 612, ParamSpec, Concatenate
Projects
None yet
7 participants