microsoft / pyright

Static Type Checker for Python
Other
13.53k stars 1.5k forks source link

a decorated function calling itself gives inconsistent typing #9547

Closed adhami3310 closed 6 hours ago

adhami3310 commented 6 hours ago
from typing import Callable

class Printer[**P]:
    def print_args(self, *args: P.args, **kwargs: P.kwargs):
        print(args, kwargs)

def make_into_printer[**P](func: Callable[P, None]) -> Printer[P]:
    return Printer()

@make_into_printer
def calculate_math(x: int):
    calculate_math.print_args(3)

Here pyright (through playground) gives:

Cannot access attribute "print_args" for class "function"
  Attribute "print_args" is unknown  (reportFunctionMemberAccess)

and MyPy is happy.

However, this is not very consistent, as revealing the type of calculate_math gives Printer[(x: int)]. So there's some notion of it being wrapped, but it's not being consistent. It also will complain if you are giving the wrong arguments to print_args.

This function can technically be called while it's being decorated (within the decorator), although when that happens, the function errors as the name is yet to be defined. So the only type that variable can have, is the return type of the decorator function.

I haven't found any issues when we're outside the function.

adhami3310 commented 6 hours ago

Note: The generic stuff is actually important to cause this error. If you replace Callable with Any, things work as expected.

erictraut commented 6 hours ago

You need to add an explicit return type annotation to the decorated function in this case. Pyright is generally able to infer return types for functions, but in this case you are using a recursive definition, which leads to a circular dependency that pyright cannot resolve. The return type of the pre-decorated function must be known before the decorator can be applied.

@make_into_printer
def calculate_math(x: int) -> None:
    calculate_math.print_args(3)