Propagating ParamSpec through wrappers differ by class & function
- Dominant language
- Python
- Stars
- 15.6k
- Forks
- 1.8k
- Avg merge
- 12h 13m
- Merged PRs (30d)
- 52
Description
**Describe the bug**
When using two levels of function wrappers, pyright is unable to correctly determine the function signature of wrapped function when the outer wrapper is expressed as a class. This seems to only apply when the inner wrapper (`inner` in code) calls the inner function directly. I.e in addition to the inner function, the inner wrapper also takes the same arguments as the inner function.
Expected behavior: Expressing the outer wrapper as a function or a class (`outer_func`/`outer_class`) should behave similarly when determining the wrapped function's signature. The class-based method should correctly determine the wrapped function's signature; the same/equivalent as `inner`.
**Code or Screenshots**
```python
from typing import Callable
### Outer Wrapper ###
def outer_func[**P, T](f: Callable[P, T]) -> Callable[P, T]:
def _outer_func(*args: P.args, **kwargs: P.kwargs) -> T:
return f(*args, **kwargs)
return _outer_func
class outer_class[**P, T]:
def __init__(self, f: Callable[P, T]):
self._f = f
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
return self._f(*args, **kwargs)
### Inner Wrapper ###
def inner[**P, T](f: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
return f(*args, **kwargs)
### Nested Wrappers ###
nested_func = outer_func(inner)
nested_class = outer_class(inner)
### Inner functions ###
def foo() -> None: print("foo")
def bar(x: int) -> int: return x
### Demo ###
inner(foo) # OK
nested_func(foo) # OK
nested_class(foo) # ERR: Arguments for ParamSpec "P@inner" are missing
assert inner(bar, 1) == 1 # OK
assert nested_func(bar, 1) == 1 # OK
assert nested_class(bar, 1) == 1 # ERR: Expected 1 positional argument
```
**VS Code extension or command-line**
VS Code Extension, ms-python.vscode-pylance (2025.6.2)
Contributor guide
Research direction
Reproduce the supplied nested-wrapper example with Pyright through the command line or VS Code extension, comparing outer_func and outer_class diagnostics. Trace how ParamSpec signatures are inferred for the class-based __call__ path; done when nested_class accepts both foo and bar with the same inferred signature and diagnostics as nested_func.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 50/100