diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 27f462b7d00f..edda15c54381 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -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): diff --git a/mypy/messages.py b/mypy/messages.py index cc9245144328..021ac5c0f1a7 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -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: diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index 84c4684d46ab..1574bb849e0a 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -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