Skip to content

Commit 5cdb753

Browse files
committed
Reject ParamSpec-typed callables calls with insufficient arguments
1 parent 93dac05 commit 5cdb753

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

mypy/checkexpr.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1745,7 +1745,11 @@ def check_callable_call(
17451745
)
17461746

17471747
param_spec = callee.param_spec()
1748-
if param_spec is not None and arg_kinds == [ARG_STAR, ARG_STAR2]:
1748+
if (
1749+
param_spec is not None
1750+
and arg_kinds == [ARG_STAR, ARG_STAR2]
1751+
and len(formal_to_actual) == 2
1752+
):
17491753
arg1 = self.accept(args[0])
17501754
arg2 = self.accept(args[1])
17511755
if (
@@ -2351,6 +2355,10 @@ def check_argument_count(
23512355
# Positional argument when expecting a keyword argument.
23522356
self.msg.too_many_positional_arguments(callee, context)
23532357
ok = False
2358+
elif callee.param_spec() is not None:
2359+
if not formal_to_actual[i]:
2360+
self.msg.too_few_arguments(callee, context, actual_names)
2361+
ok = False
23542362
return ok
23552363

23562364
def check_for_extra_actual_arguments(

test-data/unit/check-parameter-specification.test

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,3 +2204,54 @@ parametrize(_test, Case(1, b=2), Case(3, b=4))
22042204
parametrize(_test, Case(1, 2), Case(3))
22052205
parametrize(_test, Case(1, 2), Case(3, b=4))
22062206
[builtins fixtures/paramspec.pyi]
2207+
2208+
[case testRunParamSpecInsufficientArgs]
2209+
from typing_extensions import ParamSpec, Concatenate
2210+
from typing import Callable
2211+
2212+
_P = ParamSpec("_P")
2213+
2214+
def run(predicate: Callable[_P, str], *args: _P.args, **kwargs: _P.kwargs) -> None:
2215+
predicate() # E: Too few arguments
2216+
predicate(*args) # E: Too few arguments
2217+
predicate(**kwargs) # E: Too few arguments
2218+
predicate(*args, **kwargs)
2219+
2220+
[builtins fixtures/paramspec.pyi]
2221+
2222+
[case testRunParamSpecConcatenateInsufficientArgs]
2223+
from typing_extensions import ParamSpec, Concatenate
2224+
from typing import Callable
2225+
2226+
_P = ParamSpec("_P")
2227+
2228+
def run(predicate: Callable[Concatenate[int, _P], str], *args: _P.args, **kwargs: _P.kwargs) -> None:
2229+
predicate() # E: Too few arguments
2230+
predicate(1) # E: Too few arguments
2231+
predicate(1, *args) # E: Too few arguments
2232+
predicate(1, *args) # E: Too few arguments
2233+
predicate(1, **kwargs) # E: Too few arguments
2234+
predicate(*args, **kwargs) # E: Argument 1 has incompatible type "*_P.args"; expected "int"
2235+
predicate(1, *args, **kwargs)
2236+
2237+
[builtins fixtures/paramspec.pyi]
2238+
2239+
[case testRunParamSpecConcatenateInsufficientArgsInDecorator]
2240+
from typing_extensions import ParamSpec, Concatenate
2241+
from typing import Callable
2242+
2243+
P = ParamSpec("P")
2244+
2245+
def decorator(fn: Callable[Concatenate[str, P], None]) -> Callable[P, None]:
2246+
def inner(*args: P.args, **kwargs: P.kwargs) -> None:
2247+
fn("value") # E: Too few arguments
2248+
fn("value", *args) # E: Too few arguments
2249+
fn("value", **kwargs) # E: Too few arguments
2250+
fn(*args, **kwargs) # E: Argument 1 has incompatible type "*P.args"; expected "str"
2251+
fn("value", *args, **kwargs)
2252+
return inner
2253+
2254+
@decorator
2255+
def foo(s: str, s2: str) -> None: ...
2256+
2257+
[builtins fixtures/paramspec.pyi]

0 commit comments

Comments
 (0)