Skip to content

Fix wrong names listed in "too few arguments" message #19354

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion test-data/unit/check-dataclasses.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
3 changes: 2 additions & 1 deletion test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 73 additions & 0 deletions test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-functools.test
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]

Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -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: ...
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-python38.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-statements.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-varargs.test
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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,))
Expand Down Expand Up @@ -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"
Expand Down