Skip to content

Commit 8252a0b

Browse files
Wuischilevkivskyi
authored andcommitted
Show closest candidates for misspellings : keyword argument case (#7888)
This fixes the following case outlined in #824 ```python def f(other: A) -> None: pass f(otter: A()) ``` `Unexpected keyword argument "otter" for "f"` is now `Unexpected keyword argument "otter" for "f"; did you mean "other"?`
1 parent 9ee7b8a commit 8252a0b

File tree

3 files changed

+69
-2
lines changed

3 files changed

+69
-2
lines changed

mypy/checkexpr.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1284,7 +1284,8 @@ def check_for_extra_actual_arguments(self,
12841284
assert actual_names, "Internal error: named kinds without names given"
12851285
act_name = actual_names[i]
12861286
assert act_name is not None
1287-
messages.unexpected_keyword_argument(callee, act_name, context)
1287+
act_type = actual_types[i]
1288+
messages.unexpected_keyword_argument(callee, act_name, act_type, context)
12881289
is_unexpected_arg_error = True
12891290
elif ((kind == nodes.ARG_STAR and nodes.ARG_STAR not in callee.arg_kinds)
12901291
or kind == nodes.ARG_STAR2):

mypy/messages.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,9 +570,24 @@ def too_many_positional_arguments(self, callee: CallableType,
570570
msg = 'Too many positional arguments' + for_function(callee)
571571
self.fail(msg, context)
572572

573-
def unexpected_keyword_argument(self, callee: CallableType, name: str,
573+
def unexpected_keyword_argument(self, callee: CallableType, name: str, arg_type: Type,
574574
context: Context) -> None:
575575
msg = 'Unexpected keyword argument "{}"'.format(name) + for_function(callee)
576+
# Suggest intended keyword, look for type match else fallback on any match.
577+
matching_type_args = []
578+
not_matching_type_args = []
579+
for i, kwarg_type in enumerate(callee.arg_types):
580+
callee_arg_name = callee.arg_names[i]
581+
if callee_arg_name is not None and callee.arg_kinds[i] != ARG_STAR:
582+
if is_subtype(arg_type, kwarg_type):
583+
matching_type_args.append(callee_arg_name)
584+
else:
585+
not_matching_type_args.append(callee_arg_name)
586+
matches = best_matches(name, matching_type_args)
587+
if not matches:
588+
matches = best_matches(name, not_matching_type_args)
589+
if matches:
590+
msg += "; did you mean {}?".format(pretty_or(matches[:3]))
576591
self.fail(msg, context, code=codes.CALL_ARG)
577592
module = find_defining_module(self.modules, callee)
578593
if module:

test-data/unit/check-kwargs.test

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,57 @@ def f(a: 'A') -> None: pass # N: "f" defined here
8686
f(b=object()) # E: Unexpected keyword argument "b" for "f"
8787
class A: pass
8888

89+
[case testKeywordMisspelling]
90+
def f(other: 'A') -> None: pass # N: "f" defined here
91+
f(otter=A()) # E: Unexpected keyword argument "otter" for "f"; did you mean "other"?
92+
class A: pass
93+
94+
[case testMultipleKeywordsForMisspelling]
95+
def f(thing : 'A', other: 'A', atter: 'A', btter: 'B') -> None: pass # N: "f" defined here
96+
f(otter=A()) # E: Unexpected keyword argument "otter" for "f"; did you mean "other" or "atter"?
97+
class A: pass
98+
class B: pass
99+
100+
[case testKeywordMisspellingDifferentType]
101+
def f(other: 'A') -> None: pass # N: "f" defined here
102+
f(otter=B()) # E: Unexpected keyword argument "otter" for "f"; did you mean "other"?
103+
class A: pass
104+
class B: pass
105+
106+
[case testKeywordMisspellingInheritance]
107+
def f(atter: 'A', btter: 'B', ctter: 'C') -> None: pass # N: "f" defined here
108+
f(otter=B()) # E: Unexpected keyword argument "otter" for "f"; did you mean "btter" or "atter"?
109+
class A: pass
110+
class B(A): pass
111+
class C: pass
112+
113+
[case testKeywordMisspellingFloatInt]
114+
def f(atter: float, btter: int) -> None: pass # N: "f" defined here
115+
x: int = 5
116+
f(otter=x) # E: Unexpected keyword argument "otter" for "f"; did you mean "btter" or "atter"?
117+
118+
[case testKeywordMisspellingVarArgs]
119+
def f(other: 'A', *atter: 'A') -> None: pass # N: "f" defined here
120+
f(otter=A()) # E: Unexpected keyword argument "otter" for "f"; did you mean "other"?
121+
class A: pass
122+
123+
[case testKeywordMisspellingOnlyVarArgs]
124+
def f(*other: 'A') -> None: pass # N: "f" defined here
125+
f(otter=A()) # E: Unexpected keyword argument "otter" for "f"
126+
class A: pass
127+
128+
[case testKeywordMisspellingVarArgsDifferentTypes]
129+
def f(other: 'B', *atter: 'A') -> None: pass # N: "f" defined here
130+
f(otter=A()) # E: Unexpected keyword argument "otter" for "f"; did you mean "other"?
131+
class A: pass
132+
class B: pass
133+
134+
[case testKeywordMisspellingVarKwargs]
135+
def f(other: 'A', **atter: 'A') -> None: pass
136+
f(otter=A()) # E: Missing positional argument "other" in call to "f"
137+
class A: pass
138+
[builtins fixtures/dict.pyi]
139+
89140
[case testKeywordArgumentsWithDynamicallyTypedCallable]
90141
from typing import Any
91142
f = None # type: Any

0 commit comments

Comments
 (0)