microsoft / pyright

Static Type Checker for Python
Other
12.7k stars 1.35k forks source link

weakref.finalize - issues with providing a lambda callback #8327

Closed Andrej730 closed 1 week ago

Andrej730 commented 1 week ago

See example below - providing already defined callback works fine but creating a lambda callback while calling weakref.finalize produces type errors (expected to work the same as with the previously defined callback)

import weakref
class A: ...
a = A()

callback = lambda *args, **kwargs: print(f"finalize: object was deleted: {args, kwargs}")
weakref.finalize(a, callback)
weakref.finalize(a, callback)
weakref.finalize(a, callback, 5, 25)
weakref.finalize(a, callback, c=45, d=45)
weakref.finalize(a, callback, 5, c=95)

# Arguments for ParamSpec "_P@__init__" are missing
weakref.finalize(a, lambda *args, **kwargs: print(f"finalize: object was deleted: {args, kwargs}"))
# Arguments for ParamSpec "_P@__init__" are missing
weakref.finalize(a, lambda *args, **kwargs: print(f"finalize: object was deleted: {args, kwargs}"), 5, 25)
# Arguments for ParamSpec "_P@__init__" are missing
weakref.finalize(a, lambda *args, **kwargs: print(f"finalize: object was deleted: {args, kwargs}"), c=45, d=45)
# Arguments for ParamSpec "_P@__init__" are missing
weakref.finalize(a, lambda *args, **kwargs: print(f"finalize: object was deleted: {args, kwargs}"), 5, c=95)
erictraut commented 1 week ago

I think pyright's behavior here is correct, so I don't consider this a bug.

Let's simplify the program to see what's going on here.

from typing import Any, Callable

def func[**P](func: Callable[P, Any], *args: P.args, **kwargs: P.kwargs):
    ...

func(lambda *args, **kwargs: None)

When a lambda is passed inline as an argument to another function, its parameter types are provided by the bidirectional inference context. In the code snippet above, the types of *args and **kwargs in the lambda are constrained to P.args and P.kwargs, respectively. They are not Any.

If your intent is for the lambda to accept any parameters, then you would need to move the lambda outside of the call expression (as you've done with callback above) or use a def statement with explicit parameter type annotations.