diff --git a/mypy/argmap.py b/mypy/argmap.py index ff7e94e93cbe..7e2ac716d309 100644 --- a/mypy/argmap.py +++ b/mypy/argmap.py @@ -3,7 +3,8 @@ from typing import List, Optional, Sequence, Callable, Set from mypy.types import ( - Type, Instance, TupleType, AnyType, TypeOfAny, TypedDictType, get_proper_type + Type, Instance, TupleType, AnyType, TypeOfAny, TypedDictType, get_proper_type, + TypeVarType, ) from mypy import nodes @@ -12,8 +13,8 @@ def map_actuals_to_formals(actual_kinds: List[int], actual_names: Optional[Sequence[Optional[str]]], formal_kinds: List[int], formal_names: Sequence[Optional[str]], - actual_arg_type: Callable[[int], - Type]) -> List[List[int]]: + actual_arg_type: Callable[[int], Type], + formal_types: List[Type]) -> List[List[int]]: """Calculate mapping between actual (caller) args and formals. The result contains a list of caller argument indexes mapping to each @@ -93,24 +94,49 @@ def map_actuals_to_formals(actual_kinds: List[int], and formal_kinds[fi] != nodes.ARG_STAR) or formal_kinds[fi] == nodes.ARG_STAR2] for ai in ambiguous_actual_kwargs: - for fi in unmatched_formals: + for fi in map_kwargs_to_formals(get_proper_type(actual_arg_type(ai)), + actual_kinds[ai], unmatched_formals, + formal_types, formal_names, formal_kinds): formal_to_actual[fi].append(ai) - return formal_to_actual +def map_kwargs_to_formals(actual_type: Type, actual_kind: int, + formals: List[int], + formal_types: List[Type], + formal_names: Sequence[Optional[str]], + formal_kinds: List[int]) -> List[int]: + """Generate the mapping between the actual **kwargs and formal parameters. Any given **kwarg + will only map to a parameter which it is type compatible with. + """ + from mypy.subtypes import is_subtype + mapped_formals = [] # type: List[int] + for fi in formals: + if formal_kinds[fi] == nodes.ARG_STAR: + continue + mapper = ArgTypeExpander() + expanded_actual_type = mapper.expand_actual_type(actual_type, actual_kind, + formal_names[fi], + formal_kinds[fi]) + formal_type = formal_types[fi] + if is_subtype(expanded_actual_type, formal_type) or isinstance(formal_type, TypeVarType): + mapped_formals.append(fi) + return mapped_formals + + def map_formals_to_actuals(actual_kinds: List[int], actual_names: Optional[Sequence[Optional[str]]], formal_kinds: List[int], formal_names: List[Optional[str]], - actual_arg_type: Callable[[int], - Type]) -> List[List[int]]: + actual_arg_type: Callable[[int], Type], + formal_types: List[Type]) -> List[List[int]]: """Calculate the reverse mapping of map_actuals_to_formals.""" formal_to_actual = map_actuals_to_formals(actual_kinds, actual_names, formal_kinds, formal_names, - actual_arg_type) + actual_arg_type, + formal_types) # Now reverse the mapping. actual_to_formal = [[] for _ in actual_kinds] # type: List[List[int]] for formal, actuals in enumerate(formal_to_actual): diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 40204e7c9ccf..a76b352bc2b8 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -50,7 +50,9 @@ from mypy import applytype from mypy import erasetype from mypy.checkmember import analyze_member_access, type_object_type -from mypy.argmap import ArgTypeExpander, map_actuals_to_formals, map_formals_to_actuals +from mypy.argmap import ( + ArgTypeExpander, map_actuals_to_formals, map_formals_to_actuals, map_kwargs_to_formals +) from mypy.checkstrformat import StringFormatterChecker from mypy.expandtype import expand_type, expand_type_by_instance, freshen_function_type_vars from mypy.util import split_module_names @@ -309,8 +311,8 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) -> formal_to_actual = map_actuals_to_formals( e.arg_kinds, e.arg_names, e.callee.arg_kinds, e.callee.arg_names, - lambda i: self.accept(e.args[i])) - + lambda i: self.accept(e.args[i]), + [AnyType(TypeOfAny.special_form)] * len(e.callee.arg_names)) arg_types = [join.join_type_list([self.accept(e.args[j]) for j in formal_to_actual[i]]) for i in range(len(e.callee.arg_kinds))] type_context = CallableType(arg_types, e.callee.arg_kinds, e.callee.arg_names, @@ -748,7 +750,8 @@ def apply_signature_hook( formal_to_actual = map_actuals_to_formals( arg_kinds, arg_names, callee.arg_kinds, callee.arg_names, - lambda i: self.accept(args[i])) + lambda i: self.accept(args[i]), + callee.arg_types) formal_arg_exprs = [[] for _ in range(num_formals)] # type: List[List[Expression]] for formal, actuals in enumerate(formal_to_actual): for actual in actuals: @@ -990,7 +993,8 @@ def check_callable_call(self, formal_to_actual = map_actuals_to_formals( arg_kinds, arg_names, callee.arg_kinds, callee.arg_names, - lambda i: self.accept(args[i])) + lambda i: self.accept(args[i]), + callee.arg_types) if callee.is_generic(): callee = freshen_function_type_vars(callee) @@ -1383,6 +1387,32 @@ def check_argument_count(self, ok = False return ok + def check_for_incompatible_kwargs(self, + callee: CallableType, + actual_types: List[Type], + actual_kinds: List[int], + context: Context, + messages: MessageBuilder, + formal_to_actual: List[List[int]]) -> None: + """Each **kwarg supplied to a callable should map to at least one formal + parameter. + """ + ambiguous_kwargs = [(i, actualt, actualk) + for i, (actualt, actualk) in enumerate(zip(actual_types, actual_kinds)) + if actualk == nodes.ARG_STAR2 + and not isinstance(get_proper_type(actualt), TypedDictType)] + for i, actualt, actualk in ambiguous_kwargs: + potential_formals = [] # type: List[int] + actual_formals = [] # type: List[int] + for fi in map_kwargs_to_formals(actualt, actualk, list(range(len(callee.arg_types))), + callee.arg_types, callee.arg_names, callee.arg_kinds): + potential_formals.append(fi) + if i in formal_to_actual[fi]: + actual_formals.append(fi) + if not actual_formals: + messages.kwargs_has_no_compatible_parameter(callee, context, i, actualt, + potential_formals) + def check_for_extra_actual_arguments(self, callee: CallableType, actual_types: List[Type], @@ -1454,6 +1484,8 @@ def check_argument_types(self, """ messages = messages or self.msg check_arg = check_arg or self.check_arg + self.check_for_incompatible_kwargs(callee, arg_types, arg_kinds, + context, messages, formal_to_actual) # Keep track of consumed tuple *arg items. mapper = ArgTypeExpander() for i, actuals in enumerate(formal_to_actual): @@ -1502,7 +1534,9 @@ def check_arg(self, isinstance(callee_type.item, Instance) and (callee_type.item.type.is_abstract or callee_type.item.type.is_protocol)): self.msg.concrete_only_call(callee_type, context) - elif not is_subtype(caller_type, callee_type): + elif not is_subtype(caller_type, callee_type) and (caller_kind != nodes.ARG_STAR2 + or isinstance(original_caller_type, + TypedDictType)): if self.chk.should_suppress_optional_error([caller_type, callee_type]): return code = messages.incompatible_argument(n, @@ -1657,7 +1691,8 @@ def has_shape(typ: Type) -> bool: for typ in overload.items(): formal_to_actual = map_actuals_to_formals(arg_kinds, arg_names, typ.arg_kinds, typ.arg_names, - lambda i: arg_types[i]) + lambda i: arg_types[i], + typ.arg_types) if self.check_argument_count(typ, arg_types, arg_kinds, arg_names, formal_to_actual, None, None): @@ -1959,7 +1994,8 @@ def erased_signature_similarity(self, arg_names, callee.arg_kinds, callee.arg_names, - lambda i: arg_types[i]) + lambda i: arg_types[i], + callee.arg_types) if not self.check_argument_count(callee, arg_types, arg_kinds, arg_names, formal_to_actual, None, None): @@ -4381,7 +4417,10 @@ def any_causes_overload_ambiguity(items: List[CallableType], actual_to_formal = [ map_formals_to_actuals( - arg_kinds, arg_names, item.arg_kinds, item.arg_names, lambda i: arg_types[i]) + arg_kinds, arg_names, + item.arg_kinds, item.arg_names, + lambda i: arg_types[i], + item.arg_types) for item in items ] diff --git a/mypy/messages.py b/mypy/messages.py index 6c1a6f734d89..cd2b5984c6c1 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -615,6 +615,29 @@ def too_many_arguments(self, callee: CallableType, context: Context) -> None: msg = 'Too many arguments' + for_function(callee) self.fail(msg, context, code=codes.CALL_ARG) + def kwargs_has_no_compatible_parameter(self, callee: CallableType, context: Context, + kwargs_index: int, kwargs_type: Type, + formals: List[int]) -> None: + callee_name = callable_name(callee) + kwargs_type_str = format_type(kwargs_type) + argument_number = kwargs_index + 1 + if formals: + formal_names = ', '.join(('"' + name + '"') + if name + else 'parameter {}'.format(fi) + for fi, name in enumerate(callee.arg_names[fi] + for fi in formals)) + msg = ('Argument {} in call to {} with type {} cannot unpack into any expected ' + 'parameters; {} in use'. + format(argument_number, callee_name, kwargs_type_str, formal_names)) + else: + msg = ('Argument {} in call to {} cannot unpack into any parameters; ' + 'no parameter accepts type {}'. + format(argument_number, + callee_name, + kwargs_type_str)) + self.fail(msg, context, code=codes.CALL_ARG) + def too_many_arguments_from_typed_dict(self, callee: CallableType, arg_type: TypedDictType, diff --git a/mypy/stats.py b/mypy/stats.py index 17725ac86bdc..6f9f2a4ee7c6 100644 --- a/mypy/stats.py +++ b/mypy/stats.py @@ -250,7 +250,8 @@ def record_callable_target_precision(self, o: CallExpr, callee: CallableType) -> o.arg_names, callee.arg_kinds, callee.arg_names, - lambda n: typemap[o.args[n]]) + lambda n: typemap[o.args[n]], + callee.arg_types) for formals in actual_to_formal: for n in formals: formal = get_proper_type(callee.arg_types[n]) diff --git a/mypy/suggestions.py b/mypy/suggestions.py index 0a41b134db6f..f296e3baf70d 100644 --- a/mypy/suggestions.py +++ b/mypy/suggestions.py @@ -158,7 +158,8 @@ def visit_call_expr(self, o: CallExpr) -> None: formal_to_actual = map_actuals_to_formals( o.arg_kinds, o.arg_names, typ.arg_kinds, typ.arg_names, - lambda n: AnyType(TypeOfAny.special_form)) + lambda n: AnyType(TypeOfAny.special_form), + typ.arg_types) for i, args in enumerate(formal_to_actual): for arg_idx in args: diff --git a/mypy/test/testinfer.py b/mypy/test/testinfer.py index 0c2f55bc69ad..0f3773b3e77c 100644 --- a/mypy/test/testinfer.py +++ b/mypy/test/testinfer.py @@ -181,7 +181,8 @@ def assert_map(self, caller_names, callee_kinds, callee_names, - lambda i: AnyType(TypeOfAny.special_form)) + lambda i: AnyType(TypeOfAny.special_form), + [AnyType(TypeOfAny.special_form)] * len(callee_kinds)) assert_equal(result, expected) def assert_vararg_map(self, @@ -195,7 +196,8 @@ def assert_vararg_map(self, [], callee_kinds, [], - lambda i: vararg_type) + lambda i: vararg_type, + [AnyType(TypeOfAny.special_form)] * len(callee_kinds)) assert_equal(result, expected) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index e4ca731d2420..d2be4655a4b1 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -13,7 +13,7 @@ ) from mypy.nodes import ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, op_methods -from mypy.types import AnyType, TypeOfAny +from mypy.types import AnyType, TypeOfAny, Type from mypy.checkexpr import map_actuals_to_formals from mypyc.ir.ops import ( @@ -333,11 +333,13 @@ def native_args_to_positional(self, sig_arg_kinds = [arg.kind for arg in sig.args] sig_arg_names = [arg.name for arg in sig.args] + sig_arg_types = [AnyType(TypeOfAny.special_form)] * len(sig_arg_kinds) # type: List[Type] formal_to_actual = map_actuals_to_formals(arg_kinds, arg_names, sig_arg_kinds, sig_arg_names, - lambda n: AnyType(TypeOfAny.special_form)) + lambda n: AnyType(TypeOfAny.special_form), + sig_arg_types) # Flatten out the arguments, loading error values for default # arguments, constructing tuples/dicts for star args, and diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index 339d0ce863a7..58263669a828 100644 --- a/test-data/unit/check-columns.test +++ b/test-data/unit/check-columns.test @@ -72,7 +72,9 @@ def g(**x: int) -> None: pass a = [''] f(*a) # E:4: Argument 1 to "f" has incompatible type "*List[str]"; expected "int" b = {'x': 'y'} -g(**b) # E:5: Argument 1 to "g" has incompatible type "**Dict[str, str]"; expected "int" +# TODO: "too many arguments" error will not appear after #9629 is merged +g(**b) # E:1: Too many arguments for "g" \ + # E:1: Argument 1 in call to "g" cannot unpack into any parameters; no parameter accepts type "Dict[str, str]" [builtins fixtures/dict.pyi] [case testColumnsMultipleStatementsPerLine] diff --git a/test-data/unit/check-ctypes.test b/test-data/unit/check-ctypes.test index f6e55a451794..49b23029994d 100644 --- a/test-data/unit/check-ctypes.test +++ b/test-data/unit/check-ctypes.test @@ -182,6 +182,7 @@ import ctypes intarr4 = ctypes.c_int * 4 x = {"a": 1, "b": 2} -intarr4(**x) # E: Too many arguments for "Array" +intarr4(**x) # E: Too many arguments for "Array" \ + # E: Argument 1 in call to "Array" cannot unpack into any parameters; no parameter accepts type "Dict[str, int]" [builtins fixtures/floatdict.pyi] diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 4eb52be6f8bd..edd08e4f3609 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -2217,7 +2217,8 @@ kw2 = {'x': ''} d2 = dict(it, **kw2) d2() # E: "Dict[str, object]" not callable -d3 = dict(it, **kw2) # type: Dict[str, int] # E: Argument 2 to "dict" has incompatible type "**Dict[str, str]"; expected "int" +d3 = dict(it, **kw2) # type: Dict[str, int] \ + # E: Argument 2 in call to "dict" cannot unpack into any parameters; no parameter accepts type "Dict[str, str]" [builtins fixtures/dict.pyi] [case testDictFromIterableAndStarStarArgs2] diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index 96669e7eea36..de3aae73d25e 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -299,8 +299,11 @@ d = None # type: Dict[str, A] f(**d) f(x=A(), **d) d2 = None # type: Dict[str, B] -f(**d2) # E: Argument 1 to "f" has incompatible type "**Dict[str, B]"; expected "A" -f(x=A(), **d2) # E: Argument 2 to "f" has incompatible type "**Dict[str, B]"; expected "A" +# TODO: "too many arguments" errors will not appear after #9629 is merged +f(**d2) # E: Too many arguments for "f" \ + # E: Argument 1 in call to "f" cannot unpack into any parameters; no parameter accepts type "Dict[str, B]" +f(x=A(), **d2) # E: Too many arguments for "f" \ + # E: Argument 2 in call to "f" cannot unpack into any parameters; no parameter accepts type "Dict[str, B]" class A: pass class B: pass [builtins fixtures/dict.pyi] @@ -363,7 +366,7 @@ def f(a: 'A', b: 'B') -> None: pass d = None # type: Dict[str, Any] f(**d) d2 = None # type: Dict[str, A] -f(**d2) # E: Argument 1 to "f" has incompatible type "**Dict[str, A]"; expected "B" +f(**d2) # E: Too few arguments for "f" class A: pass class B: pass [builtins fixtures/dict.pyi] @@ -396,7 +399,8 @@ class A: pass from typing import Any, Dict def f(*args: 'A') -> None: pass d = None # type: Dict[Any, Any] -f(**d) # E: Too many arguments for "f" +f(**d) # E: Too many arguments for "f" \ + # E: Argument 1 in call to "f" cannot unpack into any parameters; no parameter accepts type "Dict[Any, Any]" class A: pass [builtins fixtures/dict.pyi] @@ -440,7 +444,10 @@ f(**a) # okay b = {'': ''} f(b) # E: Argument 1 to "f" has incompatible type "Dict[str, str]"; expected "int" -f(**b) # E: Argument 1 to "f" has incompatible type "**Dict[str, str]"; expected "int" +# TODO: "too many arguments" error will not appear after #9629 is merged +f(**b) # E: Too many arguments for "f" \ + # E: Too few arguments for "f" \ + # E: Argument 1 in call to "f" cannot unpack into any parameters; no parameter accepts type "Dict[str, str]" c = {0: 0} f(**c) # E: Keywords must be strings @@ -451,6 +458,76 @@ def f(**k): pass f(*(1, 2)) # E: Too many arguments for "f" [builtins fixtures/dict.pyi] +[case testCallKwargsDifferentTypes] +from typing import Dict +class A: pass +class B: pass +class C: pass + +ad = None # type: Dict[str, A] +bd = None # type: Dict[str, B] +cd = None # type: Dict[str, C] + +# TODO: "too many arguments" errors will not appear after #9629 is merged + +def f1(a: A, b: B): pass +f1(**ad, **bd) +f1(**bd, **ad) +f1(a=A(), **ad, **bd) # E: Too many arguments for "f1" \ + # E: Argument 2 in call to "f1" with type "Dict[str, A]" cannot unpack into any expected parameters; "a" in use +f1(b=B(), **ad, **bd) # E: Too many arguments for "f1" \ + # E: Argument 3 in call to "f1" with type "Dict[str, B]" cannot unpack into any expected parameters; "b" in use +f1(a=A(), b=B(), **ad, **bd) # E: Too many arguments for "f1" \ + # E: Argument 3 in call to "f1" with type "Dict[str, A]" cannot unpack into any expected parameters; "a" in use \ + # E: Argument 4 in call to "f1" with type "Dict[str, B]" cannot unpack into any expected parameters; "b" in use +f1(**ad) # E: Too few arguments for "f1" +f1(b=B(), **ad) +f1(**bd) # E: Too few arguments for "f1" +f1(A(), **bd) +f1(**ad, **bd, **cd) # E: Too many arguments for "f1" \ + # E: Argument 3 in call to "f1" cannot unpack into any parameters; no parameter accepts type "Dict[str, C]" + +def f2(a: A = A(), b: B = B()): pass +f2(**ad, **bd) +f2(**bd, **ad) +f2(a=A(), **ad, **bd) # E: Too many arguments for "f2" \ + # E: Argument 2 in call to "f2" with type "Dict[str, A]" cannot unpack into any expected parameters; "a" in use +f2(b=B(), **ad, **bd) # E: Too many arguments for "f2" \ + # E: Argument 3 in call to "f2" with type "Dict[str, B]" cannot unpack into any expected parameters; "b" in use +f2(a=A(), b=B(), **ad, **bd) # E: Too many arguments for "f2" \ + # E: Argument 3 in call to "f2" with type "Dict[str, A]" cannot unpack into any expected parameters; "a" in use \ + # E: Argument 4 in call to "f2" with type "Dict[str, B]" cannot unpack into any expected parameters; "b" in use +f2(**ad) +f2(A(), **ad) # E: Too many arguments for "f2" \ + # E: Argument 2 in call to "f2" with type "Dict[str, A]" cannot unpack into any expected parameters; "a" in use +f2(**bd) +f2(b=B(), **bd) # E: Too many arguments for "f2" \ + # E: Argument 2 in call to "f2" with type "Dict[str, B]" cannot unpack into any expected parameters; "b" in use +f2(**ad, **bd, **cd) # E: Too many arguments for "f2" \ + # E: Argument 3 in call to "f2" cannot unpack into any parameters; no parameter accepts type "Dict[str, C]" +f2(**cd) # E: Too many arguments for "f2" \ + # E: Argument 1 in call to "f2" cannot unpack into any parameters; no parameter accepts type "Dict[str, C]" + +def f3(a: A = A(), **bs: B): pass +f3(**ad, **bd) +f3(**bd, **ad) + +f3(a=A(), **ad, **bd) # E: Too many arguments for "f3" \ + # E: Argument 2 in call to "f3" with type "Dict[str, A]" cannot unpack into any expected parameters; "a" in use +f3(b=B(), **ad, **bd) +f3(a=A(), b=B(), **ad, **bd) # E: Too many arguments for "f3" \ + # E: Argument 3 in call to "f3" with type "Dict[str, A]" cannot unpack into any expected parameters; "a" in use +f3(**ad) +f3(A(), **ad) # E: Too many arguments for "f3" \ + # E: Argument 2 in call to "f3" with type "Dict[str, A]" cannot unpack into any expected parameters; "a" in use +f3(**bd) +f3(b=B(), **bd) +f3(**ad, **bd, **cd) # E: Too many arguments for "f3" \ + # E: Argument 3 in call to "f3" cannot unpack into any parameters; no parameter accepts type "Dict[str, C]" +f3(**cd) # E: Too many arguments for "f3" \ + # E: Argument 1 in call to "f3" cannot unpack into any parameters; no parameter accepts type "Dict[str, C]" +[builtins fixtures/dict.pyi] + [case testUnexpectedMethodKwargInNestedClass] class A: class B: @@ -485,7 +562,7 @@ def g(arg: int = 0, **kwargs: object) -> None: d = {} # type: Dict[str, object] f(**d) -g(**d) # E: Argument 1 to "g" has incompatible type "**Dict[str, object]"; expected "int" +g(**d) m = {} # type: Mapping[str, object] f(**m) diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 2c474f389ad4..b8156dfa04df 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -1584,8 +1584,11 @@ d = None # type: Dict[Any, Any] f1(**td, **d) f1(**d, **td) -f2(**td, **d) # E: Too many arguments for "f2" -f2(**d, **td) # E: Too many arguments for "f2" +# TODO: the "too many arguments" errors will go away with #9269 +f2(**td, **d) # E: Too many arguments for "f2" \ + # E: Argument 2 in call to "f2" with type "Dict[Any, Any]" cannot unpack into any expected parameters; "x", "y" in use +f2(**d, **td) # E: Too many arguments for "f2" \ + # E: Argument 1 in call to "f2" with type "Dict[Any, Any]" cannot unpack into any expected parameters; "x", "y" in use [builtins fixtures/dict.pyi] [case testTypedDictNonMappingMethods]