Skip to content

Commit

Permalink
Show closest candidates for misspellings : keyword argument case (#7888)
Browse files Browse the repository at this point in the history
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"?`
  • Loading branch information
Wuisch authored and ilevkivskyi committed Nov 7, 2019
1 parent 9ee7b8a commit 8252a0b
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 2 deletions.
3 changes: 2 additions & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1284,7 +1284,8 @@ def check_for_extra_actual_arguments(self,
assert actual_names, "Internal error: named kinds without names given"
act_name = actual_names[i]
assert act_name is not None
messages.unexpected_keyword_argument(callee, act_name, context)
act_type = actual_types[i]
messages.unexpected_keyword_argument(callee, act_name, act_type, context)
is_unexpected_arg_error = True
elif ((kind == nodes.ARG_STAR and nodes.ARG_STAR not in callee.arg_kinds)
or kind == nodes.ARG_STAR2):
Expand Down
17 changes: 16 additions & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,9 +570,24 @@ def too_many_positional_arguments(self, callee: CallableType,
msg = 'Too many positional arguments' + for_function(callee)
self.fail(msg, context)

def unexpected_keyword_argument(self, callee: CallableType, name: str,
def unexpected_keyword_argument(self, callee: CallableType, name: str, arg_type: Type,
context: Context) -> None:
msg = 'Unexpected keyword argument "{}"'.format(name) + for_function(callee)
# Suggest intended keyword, look for type match else fallback on any match.
matching_type_args = []
not_matching_type_args = []
for i, kwarg_type in enumerate(callee.arg_types):
callee_arg_name = callee.arg_names[i]
if callee_arg_name is not None and callee.arg_kinds[i] != ARG_STAR:
if is_subtype(arg_type, kwarg_type):
matching_type_args.append(callee_arg_name)
else:
not_matching_type_args.append(callee_arg_name)
matches = best_matches(name, matching_type_args)
if not matches:
matches = best_matches(name, not_matching_type_args)
if matches:
msg += "; did you mean {}?".format(pretty_or(matches[:3]))
self.fail(msg, context, code=codes.CALL_ARG)
module = find_defining_module(self.modules, callee)
if module:
Expand Down
51 changes: 51 additions & 0 deletions test-data/unit/check-kwargs.test
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,57 @@ def f(a: 'A') -> None: pass # N: "f" defined here
f(b=object()) # E: Unexpected keyword argument "b" for "f"
class A: pass

[case testKeywordMisspelling]
def f(other: 'A') -> None: pass # N: "f" defined here
f(otter=A()) # E: Unexpected keyword argument "otter" for "f"; did you mean "other"?
class A: pass

[case testMultipleKeywordsForMisspelling]
def f(thing : 'A', other: 'A', atter: 'A', btter: 'B') -> None: pass # N: "f" defined here
f(otter=A()) # E: Unexpected keyword argument "otter" for "f"; did you mean "other" or "atter"?
class A: pass
class B: pass

[case testKeywordMisspellingDifferentType]
def f(other: 'A') -> None: pass # N: "f" defined here
f(otter=B()) # E: Unexpected keyword argument "otter" for "f"; did you mean "other"?
class A: pass
class B: pass

[case testKeywordMisspellingInheritance]
def f(atter: 'A', btter: 'B', ctter: 'C') -> None: pass # N: "f" defined here
f(otter=B()) # E: Unexpected keyword argument "otter" for "f"; did you mean "btter" or "atter"?
class A: pass
class B(A): pass
class C: pass

[case testKeywordMisspellingFloatInt]
def f(atter: float, btter: int) -> None: pass # N: "f" defined here
x: int = 5
f(otter=x) # E: Unexpected keyword argument "otter" for "f"; did you mean "btter" or "atter"?

[case testKeywordMisspellingVarArgs]
def f(other: 'A', *atter: 'A') -> None: pass # N: "f" defined here
f(otter=A()) # E: Unexpected keyword argument "otter" for "f"; did you mean "other"?
class A: pass

[case testKeywordMisspellingOnlyVarArgs]
def f(*other: 'A') -> None: pass # N: "f" defined here
f(otter=A()) # E: Unexpected keyword argument "otter" for "f"
class A: pass

[case testKeywordMisspellingVarArgsDifferentTypes]
def f(other: 'B', *atter: 'A') -> None: pass # N: "f" defined here
f(otter=A()) # E: Unexpected keyword argument "otter" for "f"; did you mean "other"?
class A: pass
class B: pass

[case testKeywordMisspellingVarKwargs]
def f(other: 'A', **atter: 'A') -> None: pass
f(otter=A()) # E: Missing positional argument "other" in call to "f"
class A: pass
[builtins fixtures/dict.pyi]

[case testKeywordArgumentsWithDynamicallyTypedCallable]
from typing import Any
f = None # type: Any
Expand Down

0 comments on commit 8252a0b

Please sign in to comment.