diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 603acbe84f0f..5534fb7e586a 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2389,14 +2389,26 @@ def check_argument_count( ) # Check for too many or few values for formals. + missing_contiguous_pos_block: list[int] = [] + seen_kw = False for i, kind in enumerate(callee.arg_kinds): mapped_args = formal_to_actual[i] + seen_kw = ( + seen_kw + or kind == ArgKind.ARG_NAMED_OPT + or any(actual_kinds[k].is_named(star=True) for k in mapped_args) + ) if kind.is_required() and not mapped_args and not is_unexpected_arg_error: # No actual for a mandatory formal - if kind.is_positional(): - self.msg.too_few_arguments(callee, context, actual_names) - if object_type and callable_name and "." in callable_name: - self.missing_classvar_callable_note(object_type, callable_name, context) + if ( + kind.is_positional() + and not seen_kw + and ( + not missing_contiguous_pos_block + or missing_contiguous_pos_block[-1] == i - 1 + ) + ): + missing_contiguous_pos_block.append(i) else: argname = callee.arg_names[i] or "?" self.msg.missing_named_argument(callee, context, argname) @@ -2432,6 +2444,21 @@ def check_argument_count( if actual_kinds[mapped_args[0]] == nodes.ARG_STAR2 and paramspec_entries > 1: self.msg.fail("ParamSpec.kwargs should only be passed once", context) ok = False + if missing_contiguous_pos_block: + # To generate a correct message, expand kwargs manually. If a name was + # missing from the call but doesn't belong to continuous positional prefix, + # it was already reported as a missing kwarg. All args before first prefix + # item are guaranteed to have been passed positionally. + passed_or_reported_names = [ + name + for name in callee.arg_names[missing_contiguous_pos_block[-1] + 1 :] + # None may be there if it was an optional posonly param + if name is not None + ] + names_to_use = [None] * missing_contiguous_pos_block[0] + passed_or_reported_names + self.msg.too_few_arguments(callee, context, names_to_use) + if object_type and callable_name and "." in callable_name: + self.missing_classvar_callable_note(object_type, callable_name, context) return ok def check_for_extra_actual_arguments( diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index bf6c51e86446..981d2d8520ec 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2008,7 +2008,7 @@ class D: def __set__(self, inst, v, other): pass class A: f = D() -A().f = 'x' # E: Too few arguments for "__set__" +A().f = 'x' # E: Missing positional argument "other" in call to "__set__" [case testDescriptorDunderSetWrongArgTypes] class D: @@ -2036,7 +2036,7 @@ class D: def __get__(self, inst, own, other): pass class A: f = D() -A().f = 'x' # E: Too few arguments for "__get__" +A().f = 'x' # E: Missing positional argument "other" in call to "__get__" [case testDescriptorDunderGetWrongArgTypeForInstance] from typing import Any diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 2ead202bd6af..8c07a09c443d 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -425,10 +425,11 @@ class Application: name: str = 'Unnamed' rating: int = field(kw_only=False) # E: Attributes without a default cannot follow attributes with one +reveal_type(Application) # N: Revealed type is "def (name: builtins.str =, rating: builtins.int) -> __main__.Application" Application(name='name', rating=5) Application('name', 123) Application('name', rating=123) -Application() # E: Missing positional argument "name" in call to "Application" +Application() # E: Too few arguments for "Application" Application('name') # E: Too few arguments for "Application" [builtins fixtures/dataclasses.pyi] diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index d6e3366401dd..9f573b581d00 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -242,7 +242,8 @@ g() # E: Missing named argument "x" for "g" [call-arg] def h(x: int, y: int, z: int) -> None: pass h(y=1, z=1) # E: Missing positional argument "x" in call to "h" [call-arg] -h(y=1) # E: Missing positional arguments "x", "z" in call to "h" [call-arg] +h(y=1) # E: Missing named argument "z" for "h" [call-arg] \ + # E: Missing positional argument "x" in call to "h" [call-arg] [case testErrorCodeArgType] def f(x: int) -> None: pass diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index ceb7af433dce..880a9705e4d9 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2467,6 +2467,79 @@ f(b=4) # E: Missing positional argument "a" in call to "f" def f(a, b, c, d=None) -> None: pass f(1, d=3) # E: Missing positional arguments "b", "c" in call to "f" +[case testMissingArgumentsErrorFromTypedDict] +from typing import Optional,TypedDict + +class DA(TypedDict): + a: int +class DB(TypedDict): + b: int +class DC(TypedDict): + c: int + +class DAB(DA, DB): pass +class DAC(DA, DC): pass +class DBC(DB, DC): pass +class DABC(DA, DB, DC): pass + +da: DA +db: DB +dc: DC +dab: DAB +dac: DAC +dbc: DBC +dabc: DABC + +def f(a: int, b: int, c: int, d: Optional[int] = None) -> None: pass + +f(**da) # E: Missing named argument "b" for "f" \ + # E: Missing named argument "c" for "f" +f(**db) # E: Missing named argument "c" for "f" \ + # E: Missing positional argument "a" in call to "f" +f(**dc) # E: Missing positional arguments "a", "b" in call to "f" +f(**dab) # E: Missing named argument "c" for "f" +f(**dac) # E: Missing named argument "b" for "f" +f(**dbc) # E: Missing positional argument "a" in call to "f" +f(**dabc) + +def g(a: int, /, b: int, c: int) -> None: pass + +g(**da) # E: Extra argument "a" from **args for "g" +g(0, **da) # E: Extra argument "a" from **args for "g" +g(**db) # E: Missing named argument "c" for "g" \ + # E: Too few arguments for "g" +g(0, **db) # E: Missing named argument "c" for "g" +g(**dc) # E: Too few arguments for "g" +g(0, **dc) # E: Missing positional argument "b" in call to "g" +g(**dab) # E: Extra argument "a" from **args for "g" +g(0, **dab) # E: Extra argument "a" from **args for "g" +g(**dac) # E: Extra argument "a" from **args for "g" +g(0, **dac) # E: Extra argument "a" from **args for "g" +g(**dbc) # E: Too few arguments for "g" +g(0, **dbc) +g(**dabc) # E: Extra argument "a" from **args for "g" + +def h(a: int, b: int, /, c: int) -> None: pass + +h(**da) # E: Extra argument "a" from **args for "h" +h(0, **da) # E: Extra argument "a" from **args for "h" +h(0, 1, **da) # E: Extra argument "a" from **args for "h" +h(**db) # E: Extra argument "b" from **args for "h" +h(0, **db) # E: Extra argument "b" from **args for "h" +h(0, 1, **db) # E: Extra argument "b" from **args for "h" +h(**dc) # E: Too few arguments for "h" +h(0, **dc) # E: Too few arguments for "h" +h(0, 1, **dc) +h(**dab) # E: Extra argument "b" from **args for "h" +h(0, **dab) # E: Extra argument "b" from **args for "h" +h(**dac) # E: Extra argument "a" from **args for "h" +h(0, **dac) # E: Extra argument "a" from **args for "h" +h(**dbc) # E: Extra argument "b" from **args for "h" +h(0, **dbc) # E: Extra argument "b" from **args for "h" +h(**dabc) # E: Extra argument "b" from **args for "h" +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testReturnTypeLineNumberWithDecorator] def dec(f): pass diff --git a/test-data/unit/check-functools.test b/test-data/unit/check-functools.test index fa2cacda275d..2901eebb8eac 100644 --- a/test-data/unit/check-functools.test +++ b/test-data/unit/check-functools.test @@ -495,9 +495,9 @@ def main5(**d2: Unpack[D2]) -> None: partial(fn2, **d2)() # E: Extra argument "a2" from **args for "fn2" def main6(a2good: A2Good, a2bad: A2Bad, **d1: Unpack[D1]) -> None: - partial(fn3, **d1)() # E: Missing positional argument "a1" in call to "fn3" + partial(fn3, **d1)() # E: Missing named argument "a2" for "fn3" partial(fn3, **d1)("asdf") # E: Too many positional arguments for "fn3" \ - # E: Too few arguments for "fn3" \ + # E: Missing named argument "a2" for "fn3" \ # E: Argument 1 to "fn3" has incompatible type "str"; expected "int" partial(fn3, **d1)(a2="asdf") partial(fn3, **d1)(**a2good) @@ -530,7 +530,7 @@ reveal_type(first_kw(args=[1])) # N: Revealed type is "builtins.int" # TODO: this is indeed invalid, but the error is incomprehensible. first_kw([1]) # E: Too many positional arguments for "get" \ - # E: Too few arguments for "get" \ + # E: Missing named argument "args" for "get" \ # E: Argument 1 to "get" has incompatible type "list[int]"; expected "int" [builtins fixtures/list.pyi] diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index bfd6334b5077..a0b9f933b656 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -889,7 +889,7 @@ def h(x: int, y: str) -> None: pass g(h, 1, y='x') g(h, 1, x=1) # E: "g" gets multiple values for keyword argument "x" \ - # E: Missing positional argument "y" in call to "g" + # E: Missing named argument "y" for "g" class C[**P, T]: def m(self, *args: P.args, **kwargs: P.kwargs) -> T: ... diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test index dd3f793fd02b..4f409642a957 100644 --- a/test-data/unit/check-python38.test +++ b/test-data/unit/check-python38.test @@ -154,7 +154,7 @@ f(0, 0, kw=0) f(0, p_or_kw=0, kw=0) f(p=0, p_or_kw=0, kw=0) # E: Unexpected keyword argument "p" for "f" f(0, **d) -f(**d) # E: Missing positional argument "p_or_kw" in call to "f" +f(**d) # E: Too few arguments for "f" [builtins fixtures/dict.pyi] [case testPEP570Signatures1] diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 9ab68b32472d..6e64e299bfa8 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -453,9 +453,9 @@ if object(): if object(): raise MyErrorWithDefault if object(): - raise MyBaseError # E: Too few arguments for "MyBaseError" + raise MyBaseError # E: Missing positional argument "required" in call to "MyBaseError" if object(): - raise MyError # E: Too few arguments for "MyError" + raise MyError # E: Missing positional arguments "required1", "required2" in call to "MyError" if object(): raise MyKwError # E: Missing named argument "kwonly" for "MyKwError" [builtins fixtures/exception.pyi] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index a068a63274ca..2290eae8ba39 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -1596,7 +1596,7 @@ f1(**a) f2(**a) # E: Argument "y" to "f2" has incompatible type "str"; expected "int" f3(**a) # E: Argument "x" to "f3" has incompatible type "int"; expected "B" f4(**a) # E: Extra argument "y" from **args for "f4" -f5(**a) # E: Missing positional arguments "y", "z" in call to "f5" +f5(**a) # E: Missing named argument "z" for "f5" f6(**a) # E: Extra argument "y" from **args for "f6" f1(1, **a) # E: "f1" gets multiple values for keyword argument "x" [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 680021a166f2..0f7a4bfb96af 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -266,7 +266,7 @@ f(*(a, b, b)) # E: Argument 1 to "f" has incompatible type "*tuple[A, B, B]"; ex f(*(b, b, c)) # E: Argument 1 to "f" has incompatible type "*tuple[B, B, C]"; expected "A" f(a, *(b, b)) # E: Argument 2 to "f" has incompatible type "*tuple[B, B]"; expected "C" f(b, *(b, c)) # E: Argument 1 to "f" has incompatible type "B"; expected "A" -f(*(a, b)) # E: Missing positional arguments "b", "c" in call to "f" +f(*(a, b)) # E: Missing positional argument "c" in call to "f" f(*(a, b, c, c)) # E: Too many arguments for "f" f(a, *(b, c, c)) # E: Too many arguments for "f" f(*(a, b, c)) @@ -342,7 +342,7 @@ f(b, *(b, b)) # E: Argument 1 to "f" has incompatible type "B"; expected "A" f(b, b, *(b,)) # E: Argument 1 to "f" has incompatible type "B"; expected "A" f(a, a, *(b,)) # E: Argument 2 to "f" has incompatible type "A"; expected "B" f(a, b, *(a,)) # E: Argument 3 to "f" has incompatible type "*tuple[A]"; expected "B" -f(*()) # E: Too few arguments for "f" +f(*()) # E: Missing positional argument "a" in call to "f" f(*(a, b, b)) f(a, *(b, b)) f(a, b, *(b,)) @@ -400,7 +400,7 @@ class A: pass class B: pass a, b = None, None # type: (A, B) -f(*()) # E: Too few arguments for "f" +f(*()) # E: Missing positional argument "a" in call to "f" f(a, *[a]) # E: Argument 2 to "f" has incompatible type "*list[A]"; expected "Optional[B]" \ # E: Argument 2 to "f" has incompatible type "*list[A]"; expected "B" f(a, b, *[a]) # E: Argument 3 to "f" has incompatible type "*list[A]"; expected "B"