From bb05f3cbd442d249c93bb7de8dc73fb87da9e8b0 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 11 Nov 2016 12:47:22 -0800 Subject: [PATCH 01/18] New algorithm for checking function compatibility. The new algorithm correctly handles argument names. It pays attention to them when matching up arguments for function assignments, but not for overrides (because too many libraries vary the agument names between superclass and subclass). I'll include a full writeup in prose of the new algorithm in the pull request. Print callable types with named arguments differently from each other when important Fix importFunctionAndAssignFunction test to match var names --- mypy/checker.py | 8 +- mypy/messages.py | 36 ++++- mypy/subtypes.py | 227 ++++++++++++++++++++++------ mypy/types.py | 52 +++++++ test-data/unit/check-functions.test | 118 ++++++++++++++- test-data/unit/check-modules.test | 2 +- test-data/unit/check-super.test | 2 +- 7 files changed, 389 insertions(+), 56 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index ced9a1bd8348..88e730a53d4f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -40,8 +40,8 @@ from mypy.checkmember import map_type_from_supertype, bind_self, erase_to_bound from mypy import messages from mypy.subtypes import ( - is_subtype, is_equivalent, is_proper_subtype, is_more_precise, restrict_subtype_away, - is_subtype_ignoring_tvars + is_subtype, is_equivalent, is_proper_subtype, is_more_precise, + restrict_subtype_away, is_subtype_ignoring_tvars ) from mypy.maptype import map_instance_to_supertype from mypy.semanal import fill_typevars, set_callable_name, refers_to_fullname @@ -835,7 +835,7 @@ def check_inplace_operator_method(self, defn: FuncBase) -> None: def check_getattr_method(self, typ: CallableType, context: Context) -> None: method_type = CallableType([AnyType(), self.named_type('builtins.str')], [nodes.ARG_POS, nodes.ARG_POS], - [None], + [None, None], AnyType(), self.named_type('builtins.function')) if not is_subtype(typ, method_type): @@ -936,7 +936,7 @@ def check_override(self, override: FunctionLike, original: FunctionLike, """ # Use boolean variable to clarify code. fail = False - if not is_subtype(override, original): + if not is_subtype(override, original, ignore_positional_arg_names=True): fail = True elif (not isinstance(original, Overloaded) and isinstance(override, Overloaded) and diff --git a/mypy/messages.py b/mypy/messages.py index 9060923d611a..50504a868ae1 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -15,7 +15,7 @@ ) from mypy.nodes import ( TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases, - ARG_STAR, ARG_STAR2 + ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2 ) @@ -77,6 +77,15 @@ TYPEDDICT_ITEM_NAME_MUST_BE_STRING_LITERAL = \ 'Expected TypedDict item name to be string literal' +ARG_CONSTRUCTOR_NAMES = { + ARG_POS: "Arg", + ARG_OPT: "DefaultArg", + ARG_NAMED: "Arg", + ARG_NAMED_OPT: "DefaultArg", + ARG_STAR: "StarArg", + ARG_STAR2: "KwArg", +} + class MessageBuilder: """Helper class for reporting type checker error messages with parameters. @@ -176,8 +185,29 @@ def format(self, typ: Type, verbosity: int = 0) -> str: return_type = strip_quotes(self.format(func.ret_type)) if func.is_ellipsis_args: return 'Callable[..., {}]'.format(return_type) - arg_types = [strip_quotes(self.format(t)) for t in func.arg_types] - return 'Callable[[{}], {}]'.format(", ".join(arg_types), return_type) + arg_strings = [] + for arg_name, arg_type, arg_kind in zip( + func.arg_names, func.arg_types, func.arg_kinds): + if arg_kind == ARG_POS and arg_name is None or verbosity == 0: + arg_strings.append( + strip_quotes( + self.format( + arg_type, + verbosity = max(verbosity - 1, 0)))) + else: + constructor = ARG_CONSTRUCTOR_NAMES[arg_kind] + if arg_kind in (ARG_STAR, ARG_STAR2): + arg_strings.append("{}({})".format( + constructor, + arg_name)) + else: + arg_strings.append("{}('{}', {}, {})".format( + constructor, + arg_name, + strip_quotes(self.format(arg_type)), + arg_kind in (ARG_NAMED, ARG_NAMED_OPT))) + + return 'Callable[[{}], {}]'.format(", ".join(arg_strings), return_type) else: # Use a simple representation for function types; proper # function types may result in long and difficult-to-read diff --git a/mypy/subtypes.py b/mypy/subtypes.py index bbc8e3b0b941..2300644a71a1 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1,4 +1,4 @@ -from typing import List, Dict, Callable +from typing import List, Optional, Dict, Callable from mypy.types import ( Type, AnyType, UnboundType, TypeVisitor, ErrorType, Void, NoneTyp, @@ -10,7 +10,10 @@ # Circular import; done in the function instead. # import mypy.solve from mypy import messages, sametypes -from mypy.nodes import CONTRAVARIANT, COVARIANT +from mypy.nodes import ( + CONTRAVARIANT, COVARIANT, + ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2, +) from mypy.maptype import map_instance_to_supertype from mypy import experiments @@ -29,7 +32,8 @@ def check_type_parameter(lefta: Type, righta: Type, variance: int) -> bool: def is_subtype(left: Type, right: Type, - type_parameter_checker: TypeParameterChecker = check_type_parameter) -> bool: + type_parameter_checker: TypeParameterChecker = check_type_parameter, + *, ignore_positional_arg_names: bool = False) -> bool: """Is 'left' subtype of 'right'? Also consider Any to be a subtype of any type, and vice versa. This @@ -45,10 +49,12 @@ def is_subtype(left: Type, right: Type, or isinstance(right, ErasedType)): return True elif isinstance(right, UnionType) and not isinstance(left, UnionType): - return any(is_subtype(left, item, type_parameter_checker) + return any(is_subtype(left, item, type_parameter_checker, + ignore_positional_arg_names = ignore_positional_arg_names) for item in right.items) else: - return left.accept(SubtypeVisitor(right, type_parameter_checker)) + return left.accept(SubtypeVisitor(right, type_parameter_checker, + ignore_positional_arg_names=ignore_positional_arg_names)) def is_subtype_ignoring_tvars(left: Type, right: Type) -> bool: @@ -78,9 +84,11 @@ def h() -> None: ... class SubtypeVisitor(TypeVisitor[bool]): def __init__(self, right: Type, - type_parameter_checker: TypeParameterChecker) -> None: + type_parameter_checker: TypeParameterChecker, + *, ignore_positional_arg_names: bool = False) -> None: self.right = right self.check_type_parameter = type_parameter_checker + self.ignore_positional_arg_names = ignore_positional_arg_names # visit_x(left) means: is left (which is an instance of X) a subtype of # right? @@ -123,9 +131,9 @@ def visit_instance(self, left: Instance) -> bool: if isinstance(right, TupleType) and right.fallback.type.is_enum: return is_subtype(left, right.fallback) if isinstance(right, Instance): - if left.type._promote and is_subtype(left.type._promote, - self.right, - self.check_type_parameter): + if left.type._promote and is_subtype( + left.type._promote, self.right, self.check_type_parameter, + ignore_positional_arg_names=self.ignore_positional_arg_names): return True rname = right.type.fullname() if not left.type.has_base(rname) and rname != 'builtins.object': @@ -149,12 +157,16 @@ def visit_type_var(self, left: TypeVarType) -> bool: def visit_callable_type(self, left: CallableType) -> bool: right = self.right if isinstance(right, CallableType): - return is_callable_subtype(left, right) + return is_callable_subtype( + left, right, + ignore_positional_arg_names=self.ignore_positional_arg_names) elif isinstance(right, Overloaded): - return all(is_subtype(left, item, self.check_type_parameter) + return all(is_subtype(left, item, self.check_type_parameter, + ignore_positional_arg_names=self.ignore_positional_arg_names) for item in right.items()) elif isinstance(right, Instance): - return is_subtype(left.fallback, right) + return is_subtype(left.fallback, right, + ignore_positional_arg_names=self.ignore_positional_arg_names) elif isinstance(right, TypeType): # This is unsound, we don't check the __init__ signature. return left.is_type_obj() and is_subtype(left.ret_type, right.item) @@ -210,7 +222,8 @@ def visit_overloaded(self, left: Overloaded) -> bool: elif isinstance(right, CallableType) or is_named_instance( right, 'builtins.type'): for item in left.items(): - if is_subtype(item, right, self.check_type_parameter): + if is_subtype(item, right, self.check_type_parameter, + ignore_positional_arg_names=self.ignore_positional_arg_names): return True return False elif isinstance(right, Overloaded): @@ -218,7 +231,8 @@ def visit_overloaded(self, left: Overloaded) -> bool: if len(left.items()) != len(right.items()): return False for i in range(len(left.items())): - if not is_subtype(left.items()[i], right.items()[i], self.check_type_parameter): + if not is_subtype(left.items()[i], right.items()[i], self.check_type_parameter, + ignore_positional_arg_names=self.ignore_positional_arg_names): return False return True elif isinstance(right, UnboundType): @@ -255,9 +269,9 @@ def visit_type_type(self, left: TypeType) -> bool: def is_callable_subtype(left: CallableType, right: CallableType, - ignore_return: bool = False) -> bool: + ignore_return: bool = False, + ignore_positional_arg_names: bool = False) -> bool: """Is left a subtype of right?""" - # TODO: Support named arguments, **args, etc. # Non-type cannot be a subtype of type. if right.is_type_obj() and not left.is_type_obj(): return False @@ -286,45 +300,166 @@ def is_callable_subtype(left: CallableType, right: CallableType, if right.is_ellipsis_args: return True + right_star_type = None # type: Optional[Type] + right_star2_type = None # type: Optional[Type] + # Check argument types. - if left.min_args > right.min_args: - return False - if left.is_var_arg: - return is_var_arg_callable_subtype_helper(left, right) - if right.is_var_arg: - return False - if len(left.arg_types) < len(right.arg_types): - return False + done_with_positional = False for i in range(len(right.arg_types)): - if not is_subtype(right.arg_types[i], left.arg_types[i]): - return False - return True + right_name = right.arg_names[i] + right_kind = right.arg_kinds[i] + right_type = right.arg_types[i] + if right_kind in (ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT): + done_with_positional = True + + if not done_with_positional: + right_pos = i + else: + right_pos = None + + if right_kind == ARG_STAR: + right_star_type = right_type + # Right has an infinite series of optional positional arguments + # here. Get all further positional arguments of left, and make sure + # they're more general than their corresponding member in this + # series. Also make sure left has its own inifite series of + # optional positional arguments. + if not left.is_var_arg: + return False + j = i + while j < len(left.arg_kinds) and left.arg_kinds[j] in (ARG_POS, ARG_OPT): + left_by_position = left.argument_by_position(j) + assert left_by_position is not None + left_name, left_pos, left_type, left_required = left_by_position + # This fetches the synthetic argument that's from the *args + right_by_position = right.argument_by_position(j) + assert right_by_position is not None + right_name, right_kind, right_type, right_required = right_by_position + if not is_left_more_general(left_name, left_pos, left_type, left_required, + right_name, right_pos, right_type, right_required, + ignore_positional_arg_names): + return False + j += 1 + + continue + if right_kind == ARG_STAR2: + right_star2_type = right_type + # Right has an infinite set of optional named arguments here. Get + # all further named arguments of left and make sure they're more + # general than their corresponding member in this set. Also make + # sure left has its own infinite set of optional named arguments. + if not left.is_kw_arg: + return False + left_names = set([name for name in left.arg_names if name is not None]) + right_names = set([name for name in right.arg_names if name is not None]) + left_only_names = left_names - right_names + for name in left_only_names: + left_by_name = left.argument_by_name(name) + assert left_by_name is not None + left_name, left_pos, left_type, left_required = left_by_name + # This fetches the synthetic argument that's from the **kwargs + right_by_name = right.argument_by_name(name) + assert right_by_name is not None + right_name, right_kind, right_type, right_required = right_by_name + if not is_left_more_general(left_name, left_pos, left_type, left_required, + right_name, right_pos, right_type, right_required, + ignore_positional_arg_names): + return False + continue + right_required = right_kind in (ARG_POS, ARG_NAMED) -def is_var_arg_callable_subtype_helper(left: CallableType, right: CallableType) -> bool: - """Is left a subtype of right, assuming left has *args? + # Get possible corresponding arguments by name and by position. + if not done_with_positional: + left_by_position = left.argument_by_position(i) + else: + left_by_position = None + if right_name is not None: + left_by_name = left.argument_by_name(right_name) + else: + left_by_name = None - See also is_callable_subtype for additional assumptions we can make. - """ - left_fixed = left.max_fixed_args() - right_fixed = right.max_fixed_args() - num_fixed_matching = min(left_fixed, right_fixed) - for i in range(num_fixed_matching): - if not is_subtype(right.arg_types[i], left.arg_types[i]): + # Left must have some kind of corresponding argument. + if left_by_name is None and left_by_position is None: return False - if not right.is_var_arg: - for i in range(num_fixed_matching, len(right.arg_types)): - if not is_subtype(right.arg_types[i], left.arg_types[-1]): + # If there's ambiguity as to how to match up this argument, it's not a subtype + if (left_by_name is not None + and left_by_position is not None + and left_by_name != left_by_position): + # If we're dealing with an optional pos-only and an optional + # name-only arg, merge them. + if (left_by_position[3] is False and left_by_name[3] is False + and left_by_position[0] is None and left_by_name[1] is None + and is_equivalent(left_by_position[2], left_by_name[2])): + left_name = left_by_name[0] + left_pos = left_by_position[1] + left_type = left_by_position[2] + left_required = False + else: return False - return True - else: - for i in range(left_fixed, right_fixed): - if not is_subtype(right.arg_types[i], left.arg_types[-1]): + elif left_by_name is not None: + left_name, left_pos, left_type, left_required = left_by_name + else: + assert left_by_position is not None + left_name, left_pos, left_type, left_required = left_by_position + + if not is_left_more_general(left_name, left_pos, left_type, left_required, + right_name, right_pos, right_type, right_required, + ignore_positional_arg_names): + return False + + for i in range(len(left.arg_types)): + left_name = left.arg_names[i] + left_kind = left.arg_kinds[i] + left_type = left.arg_types[i] + # All *required* left-hand arguments must have a corresponding + # right-hand argument. The relationship between the two is handled in + # the loop above. + + # Also check that *arg types match in this loop + if left_kind == ARG_POS and right.argument_by_position(i) is None: + if left_name is None: + return False + if right.argument_by_name(left_name) is None: + return False + elif left_kind == ARG_NAMED: + if right.argument_by_name(left_name) is None: + return False + elif left_kind == ARG_STAR: + if right_star_type is not None and not is_subtype(right_star_type, left_type): return False - for i in range(right_fixed, left_fixed): - if not is_subtype(right.arg_types[-1], left.arg_types[i]): + elif left_kind == ARG_STAR2: + if right_star2_type is not None and not is_subtype(right_star2_type, left_type): return False - return is_subtype(right.arg_types[-1], left.arg_types[-1]) + return True + + +def is_left_more_general( + left_name: Optional[str], + left_pos: Optional[int], + left_type: Type, + left_required: bool, + right_name: Optional[str], + right_pos: Optional[int], + right_type: Type, + right_required: bool, + ignore_positional_arg_names: bool) -> bool: + # If right has a specific name it wants this argument to be, left must + # have the same. + if right_name is not None and left_name != right_name: + # But pay attention to whether we're ignoring positional arg names + if not ignore_positional_arg_names or right_pos is None: + return False + # If right is at a specific position, left must have the same: + if right_pos is not None and left_pos != right_pos: + return False + # Left must have a more general type + if not is_subtype(right_type, left_type): + return False + # If right's argument is optional, left's must also be. + if not right_required and left_required: + return False + return True def unify_generic_callable(type: CallableType, target: CallableType, diff --git a/mypy/types.py b/mypy/types.py index 57936bed10fb..dffe9101a02f 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -662,6 +662,58 @@ def max_fixed_args(self) -> int: n -= 1 return n + def argument_by_name(self, name: str) -> Optional[Tuple[ + Optional[str], + Optional[int], + Type, + bool]]: + seen_star = False + star2_type = None # type: Optional[Type] + for i, (arg_name, kind, typ) in enumerate( + zip(self.arg_names, self.arg_kinds, self.arg_types)): + # No more positional arguments after these. + if kind in (ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT): + seen_star = True + if kind == ARG_STAR: + continue + if kind == ARG_STAR2: + star2_type = typ + continue + if arg_name == name: + position = None if seen_star else i + return name, position, typ, kind in (ARG_POS, ARG_NAMED) + if star2_type is not None: + return name, None, star2_type, False + return None + + def argument_by_position(self, position: int) -> Optional[Tuple[ + Optional[str], + Optional[int], + Type, + bool]]: + if self.is_var_arg: + for kind, typ in zip(self.arg_kinds, self.arg_types): + if kind == ARG_STAR: + star_type = typ + break + if position >= len(self.arg_names): + if self.is_var_arg: + return None, position, star_type, False + else: + return None + name, kind, typ = ( + self.arg_names[position], + self.arg_kinds[position], + self.arg_types[position], + ) + if kind in (ARG_POS, ARG_OPT): + return name, position, typ, kind == ARG_POS + else: + if self.is_var_arg: + return None, position, star_type, False + else: + return None + def items(self) -> List['CallableType']: return [self] diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 0d0dcf74c511..15c642503653 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -21,6 +21,48 @@ b = f(a) class A: pass class B: pass +[case testKeywordOnlyArgumentOrderInsensitivity] +import typing + +class A(object): + def f(self, *, a: int, b: str) -> None: pass + +class B(A): + def f(self, *, b: str, a: int) -> None: pass + +class C(A): + def f(self, *, b: int, a: str) -> None: pass # E: Signature of "f" incompatible with supertype "A" + +[out] +main: note: In class "C": + +[case testPositionalOverridingArgumentNameInsensitivity] +import typing + +class A(object): + def f(self, a: int, b: str) -> None: pass + +class B(A): + def f(self, b: str, a: int) -> None: pass # E: Argument 1 of "f" incompatible with supertype "A" # E: Argument 2 of "f" incompatible with supertype "A" + +class C(A): + def f(self, foo: int, bar: str) -> None: pass + +[out] +main: note: In class "B": + +[case testPositionalOverridingArgumentNamesCheckedWhenMismatchingPos] +import typing + +class A(object): + def f(self, a: int, b: str) -> None: pass + +class B(A): + def f(self, b: int, a: str) -> None: pass # E: Signature of "f" incompatible with supertype "A" + +[out] +main: note: In class "B": + [case testSubtypingFunctionTypes] from typing import Callable @@ -40,6 +82,78 @@ f = f g = g h = h + +[case testSubtypingFunctionsDefaultsNames] +from typing import Callable + +def f(a: int, b: str) -> None: pass +f_nonames = None # type: Callable[[int, str], None] +def g(a: int, b: str = "") -> None: pass +def h(aa: int, b: str = "") -> None: pass + +ff_nonames = f_nonames +ff = f +gg = g +hh = h + +ff = gg +ff_nonames = ff +ff_nonames = f_nonames # reset +ff = ff_nonames # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[Arg('a', int, False), Arg('b', str, False)], None]) +ff = f # reset +gg = ff # E: Incompatible types in assignment (expression has type Callable[[Arg('a', int, False), Arg('b', str, False)], None], variable has type Callable[[Arg('a', int, False), DefaultArg('b', str, False)], None]) +gg = hh # E: Incompatible types in assignment (expression has type Callable[[Arg('aa', int, False), DefaultArg('b', str, False)], None], variable has type Callable[[Arg('a', int, False), DefaultArg('b', str, False)], None]) + +[case testSubtypingFunctionsArgsKwargs] +from typing import Any, Callable + +def everything(*args: Any, **kwargs: Any) -> None: pass +everywhere = None # type: Callable[..., None] + +def specific_1(a: int, b: str) -> None: pass +def specific_2(a: int, *, b: str) -> None: pass + +ss_1 = specific_1 +ss_2 = specific_2 +ee_def = everything +ee_var = everywhere + +ss_1 = ee_def +ss_1 = specific_1 +ss_2 = ee_def +ss_2 = specific_2 +ee_def = everywhere +ee_def = everything +ee_var = everything +ee_var = everywhere + +ee_var = specific_1 # The difference between Callable[[...], blah] and one with a *args: Any, **kwargs: Any is that the ... goes loosely both ways. +ee_def = specific_1 # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[Any, Any], None]) + +[case testLackOfNames] +def f(__a: int, __b: str) -> None: pass +def g(a: int, b: str) -> None: pass + +ff = f +gg = g + +ff = g +gg = f # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[Arg('a', int, False), Arg('b', str, False)], None]) + +[case testLackOfNamesFastparse] +# flags: --fast-parser + +def f(__a: int, __b: str) -> None: pass +def g(a: int, b: str) -> None: pass + +ff = f +gg = g + +ff = g +gg = f # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[Arg('a', int, False), Arg('b', str, False)], None]) + +[builtins fixtures/dict.pyi] + [case testFunctionTypeCompatibilityWithOtherTypes] from typing import Callable f = None # type: Callable[[], None] @@ -1401,8 +1515,10 @@ class A(Generic[t]): [case testRedefineFunction] def f(x): pass def g(x, y): pass -def h(y): pass +def h(x): pass +def j(y): pass f = h +f = j # E: Incompatible types in assignment (expression has type Callable[[Arg('y', Any, False)], Any], variable has type Callable[[Arg('x', Any, False)], Any]) f = g # E: Incompatible types in assignment (expression has type Callable[[Any, Any], Any], variable has type Callable[[Any], Any]) [case testRedefineFunction2] diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index d567763b3684..fec75b689c19 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -594,7 +594,7 @@ try: except: f = g [file m.py] -def f(y): pass +def f(x): pass [case testImportFunctionAndAssignIncompatible] try: diff --git a/test-data/unit/check-super.test b/test-data/unit/check-super.test index da7ca9060081..2993113f5426 100644 --- a/test-data/unit/check-super.test +++ b/test-data/unit/check-super.test @@ -22,7 +22,7 @@ from typing import Any class B: def f(self, y: 'A') -> None: pass class A(B): - def f(self, x: Any) -> None: + def f(self, y: Any) -> None: a, b = None, None # type: (A, B) super().f(b) # E: Argument 1 to "f" of "B" has incompatible type "B"; expected "A" super().f(a) From 8b3b788ead8b0e704ae1a91a50713cf66ae1ac1e Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 1 Dec 2016 14:38:45 -0800 Subject: [PATCH 02/18] Rename argument shorter --- mypy/checker.py | 2 +- mypy/subtypes.py | 34 ++++++++++++++--------------- test-data/unit/check-functions.test | 4 ++-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 88e730a53d4f..f13d939406e1 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -936,7 +936,7 @@ def check_override(self, override: FunctionLike, original: FunctionLike, """ # Use boolean variable to clarify code. fail = False - if not is_subtype(override, original, ignore_positional_arg_names=True): + if not is_subtype(override, original, ignore_pos_arg_names=True): fail = True elif (not isinstance(original, Overloaded) and isinstance(override, Overloaded) and diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 2300644a71a1..5437fe7f2734 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -33,7 +33,7 @@ def check_type_parameter(lefta: Type, righta: Type, variance: int) -> bool: def is_subtype(left: Type, right: Type, type_parameter_checker: TypeParameterChecker = check_type_parameter, - *, ignore_positional_arg_names: bool = False) -> bool: + *, ignore_pos_arg_names: bool = False) -> bool: """Is 'left' subtype of 'right'? Also consider Any to be a subtype of any type, and vice versa. This @@ -50,11 +50,11 @@ def is_subtype(left: Type, right: Type, return True elif isinstance(right, UnionType) and not isinstance(left, UnionType): return any(is_subtype(left, item, type_parameter_checker, - ignore_positional_arg_names = ignore_positional_arg_names) + ignore_pos_arg_names=ignore_pos_arg_names) for item in right.items) else: return left.accept(SubtypeVisitor(right, type_parameter_checker, - ignore_positional_arg_names=ignore_positional_arg_names)) + ignore_pos_arg_names=ignore_pos_arg_names)) def is_subtype_ignoring_tvars(left: Type, right: Type) -> bool: @@ -85,10 +85,10 @@ class SubtypeVisitor(TypeVisitor[bool]): def __init__(self, right: Type, type_parameter_checker: TypeParameterChecker, - *, ignore_positional_arg_names: bool = False) -> None: + *, ignore_pos_arg_names: bool = False) -> None: self.right = right self.check_type_parameter = type_parameter_checker - self.ignore_positional_arg_names = ignore_positional_arg_names + self.ignore_pos_arg_names = ignore_pos_arg_names # visit_x(left) means: is left (which is an instance of X) a subtype of # right? @@ -133,7 +133,7 @@ def visit_instance(self, left: Instance) -> bool: if isinstance(right, Instance): if left.type._promote and is_subtype( left.type._promote, self.right, self.check_type_parameter, - ignore_positional_arg_names=self.ignore_positional_arg_names): + ignore_pos_arg_names=self.ignore_pos_arg_names): return True rname = right.type.fullname() if not left.type.has_base(rname) and rname != 'builtins.object': @@ -159,14 +159,14 @@ def visit_callable_type(self, left: CallableType) -> bool: if isinstance(right, CallableType): return is_callable_subtype( left, right, - ignore_positional_arg_names=self.ignore_positional_arg_names) + ignore_pos_arg_names=self.ignore_pos_arg_names) elif isinstance(right, Overloaded): return all(is_subtype(left, item, self.check_type_parameter, - ignore_positional_arg_names=self.ignore_positional_arg_names) + ignore_pos_arg_names=self.ignore_pos_arg_names) for item in right.items()) elif isinstance(right, Instance): return is_subtype(left.fallback, right, - ignore_positional_arg_names=self.ignore_positional_arg_names) + ignore_pos_arg_names=self.ignore_pos_arg_names) elif isinstance(right, TypeType): # This is unsound, we don't check the __init__ signature. return left.is_type_obj() and is_subtype(left.ret_type, right.item) @@ -223,7 +223,7 @@ def visit_overloaded(self, left: Overloaded) -> bool: right, 'builtins.type'): for item in left.items(): if is_subtype(item, right, self.check_type_parameter, - ignore_positional_arg_names=self.ignore_positional_arg_names): + ignore_pos_arg_names=self.ignore_pos_arg_names): return True return False elif isinstance(right, Overloaded): @@ -232,7 +232,7 @@ def visit_overloaded(self, left: Overloaded) -> bool: return False for i in range(len(left.items())): if not is_subtype(left.items()[i], right.items()[i], self.check_type_parameter, - ignore_positional_arg_names=self.ignore_positional_arg_names): + ignore_pos_arg_names=self.ignore_pos_arg_names): return False return True elif isinstance(right, UnboundType): @@ -270,7 +270,7 @@ def visit_type_type(self, left: TypeType) -> bool: def is_callable_subtype(left: CallableType, right: CallableType, ignore_return: bool = False, - ignore_positional_arg_names: bool = False) -> bool: + ignore_pos_arg_names: bool = False) -> bool: """Is left a subtype of right?""" # Non-type cannot be a subtype of type. if right.is_type_obj() and not left.is_type_obj(): @@ -337,7 +337,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, right_name, right_kind, right_type, right_required = right_by_position if not is_left_more_general(left_name, left_pos, left_type, left_required, right_name, right_pos, right_type, right_required, - ignore_positional_arg_names): + ignore_pos_arg_names): return False j += 1 @@ -363,7 +363,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, right_name, right_kind, right_type, right_required = right_by_name if not is_left_more_general(left_name, left_pos, left_type, left_required, right_name, right_pos, right_type, right_required, - ignore_positional_arg_names): + ignore_pos_arg_names): return False continue @@ -405,7 +405,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, if not is_left_more_general(left_name, left_pos, left_type, left_required, right_name, right_pos, right_type, right_required, - ignore_positional_arg_names): + ignore_pos_arg_names): return False for i in range(len(left.arg_types)): @@ -443,12 +443,12 @@ def is_left_more_general( right_pos: Optional[int], right_type: Type, right_required: bool, - ignore_positional_arg_names: bool) -> bool: + ignore_pos_arg_names: bool) -> bool: # If right has a specific name it wants this argument to be, left must # have the same. if right_name is not None and left_name != right_name: # But pay attention to whether we're ignoring positional arg names - if not ignore_positional_arg_names or right_pos is None: + if not ignore_pos_arg_names or right_pos is None: return False # If right is at a specific position, left must have the same: if right_pos is not None and left_pos != right_pos: diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 15c642503653..a3e0470a5907 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -130,6 +130,8 @@ ee_var = everywhere ee_var = specific_1 # The difference between Callable[[...], blah] and one with a *args: Any, **kwargs: Any is that the ... goes loosely both ways. ee_def = specific_1 # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[Any, Any], None]) +[builtins fixtures/dict.pyi] + [case testLackOfNames] def f(__a: int, __b: str) -> None: pass def g(a: int, b: str) -> None: pass @@ -152,8 +154,6 @@ gg = g ff = g gg = f # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[Arg('a', int, False), Arg('b', str, False)], None]) -[builtins fixtures/dict.pyi] - [case testFunctionTypeCompatibilityWithOtherTypes] from typing import Callable f = None # type: Callable[[], None] From 5e68ee3c8a8469c2b0be90f12afddc9be719cf70 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 1 Dec 2016 18:10:04 -0800 Subject: [PATCH 03/18] Explain what i am doing better --- mypy/subtypes.py | 105 ++++++++++++++++++++++++----------------------- mypy/types.py | 30 +++++++------- 2 files changed, 69 insertions(+), 66 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 5437fe7f2734..d9b4a5f346e5 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1,7 +1,7 @@ from typing import List, Optional, Dict, Callable from mypy.types import ( - Type, AnyType, UnboundType, TypeVisitor, ErrorType, Void, NoneTyp, + Type, AnyType, UnboundType, TypeVisitor, ErrorType, FormalArgument, Void, NoneTyp, Instance, TypeVarType, CallableType, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, TypeList, PartialType, DeletedType, UninhabitedType, TypeType, is_named_instance ) @@ -303,22 +303,39 @@ def is_callable_subtype(left: CallableType, right: CallableType, right_star_type = None # type: Optional[Type] right_star2_type = None # type: Optional[Type] - # Check argument types. + # Match up corresponding arguments and check them for compatibility. In + # every pair (argL, argR) of corresponding arguments from L and R, argL must + # be "more general" than argR if L is to be a subtype of R. + + # Arguments are corresponding if they either share a name, share a position, + # or both. If L's corresponding argument is ambiguous, L is not a subtype of + # R. + + # If left has one corresponding argument by name and another by position, + # consider them to be one "merged" argument (and not ambiguous) if they're + # both optional, they're name-only and position-only respectively, and they + # have the same type. This rule allows functions with (*args, **kwargs) to + # properly stand in for the full domain of formal arguments that they're + # used for in practice. + + # Every argument in R must have a corresponding argument in L, and every + # required argument in L must have a corresponding arugment in R. done_with_positional = False for i in range(len(right.arg_types)): - right_name = right.arg_names[i] right_kind = right.arg_kinds[i] - right_type = right.arg_types[i] if right_kind in (ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT): done_with_positional = True + right_required = right_kind in (ARG_POS, ARG_NAMED) + right_pos = None if done_with_positional else i - if not done_with_positional: - right_pos = i - else: - right_pos = None + right_arg = FormalArgument( + right.arg_names[i], + right_pos, + right.arg_types[i], + right_required) if right_kind == ARG_STAR: - right_star_type = right_type + right_star_type = right_arg.typ # Right has an infinite series of optional positional arguments # here. Get all further positional arguments of left, and make sure # they're more general than their corresponding member in this @@ -330,20 +347,17 @@ def is_callable_subtype(left: CallableType, right: CallableType, while j < len(left.arg_kinds) and left.arg_kinds[j] in (ARG_POS, ARG_OPT): left_by_position = left.argument_by_position(j) assert left_by_position is not None - left_name, left_pos, left_type, left_required = left_by_position # This fetches the synthetic argument that's from the *args right_by_position = right.argument_by_position(j) assert right_by_position is not None - right_name, right_kind, right_type, right_required = right_by_position - if not is_left_more_general(left_name, left_pos, left_type, left_required, - right_name, right_pos, right_type, right_required, + if not is_left_more_general(left_by_position, right_by_position, ignore_pos_arg_names): return False j += 1 - continue + if right_kind == ARG_STAR2: - right_star2_type = right_type + right_star2_type = right_arg.typ # Right has an infinite set of optional named arguments here. Get # all further named arguments of left and make sure they're more # general than their corresponding member in this set. Also make @@ -356,56 +370,51 @@ def is_callable_subtype(left: CallableType, right: CallableType, for name in left_only_names: left_by_name = left.argument_by_name(name) assert left_by_name is not None - left_name, left_pos, left_type, left_required = left_by_name # This fetches the synthetic argument that's from the **kwargs right_by_name = right.argument_by_name(name) assert right_by_name is not None - right_name, right_kind, right_type, right_required = right_by_name - if not is_left_more_general(left_name, left_pos, left_type, left_required, - right_name, right_pos, right_type, right_required, + if not is_left_more_general(left_by_name, right_by_name, ignore_pos_arg_names): return False continue - right_required = right_kind in (ARG_POS, ARG_NAMED) - # Get possible corresponding arguments by name and by position. if not done_with_positional: left_by_position = left.argument_by_position(i) else: left_by_position = None - if right_name is not None: - left_by_name = left.argument_by_name(right_name) + if right_arg.name is not None: + left_by_name = left.argument_by_name(right_arg.name) else: left_by_name = None # Left must have some kind of corresponding argument. if left_by_name is None and left_by_position is None: return False + + left_arg = None # type: Optional[FormalArgument] # If there's ambiguity as to how to match up this argument, it's not a subtype if (left_by_name is not None and left_by_position is not None and left_by_name != left_by_position): # If we're dealing with an optional pos-only and an optional # name-only arg, merge them. - if (left_by_position[3] is False and left_by_name[3] is False - and left_by_position[0] is None and left_by_name[1] is None - and is_equivalent(left_by_position[2], left_by_name[2])): - left_name = left_by_name[0] - left_pos = left_by_position[1] - left_type = left_by_position[2] - left_required = False + if (left_by_position.required is False and left_by_name.required is False + and left_by_position.name is None and left_by_name.pos is None + and is_equivalent(left_by_position.typ, left_by_name.typ)): + left_arg = FormalArgument( + left_by_name.name, + left_by_position.pos, + left_by_position.typ, + required=False) else: return False - elif left_by_name is not None: - left_name, left_pos, left_type, left_required = left_by_name else: - assert left_by_position is not None - left_name, left_pos, left_type, left_required = left_by_position + left_arg = left_by_name if left_by_name is not None else left_by_position + + assert left_arg is not None - if not is_left_more_general(left_name, left_pos, left_type, left_required, - right_name, right_pos, right_type, right_required, - ignore_pos_arg_names): + if not is_left_more_general(left_arg, right_arg, ignore_pos_arg_names): return False for i in range(len(left.arg_types)): @@ -416,7 +425,6 @@ def is_callable_subtype(left: CallableType, right: CallableType, # right-hand argument. The relationship between the two is handled in # the loop above. - # Also check that *arg types match in this loop if left_kind == ARG_POS and right.argument_by_position(i) is None: if left_name is None: return False @@ -425,6 +433,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, elif left_kind == ARG_NAMED: if right.argument_by_name(left_name) is None: return False + # Also check that *args and **kwargs types match in this loop elif left_kind == ARG_STAR: if right_star_type is not None and not is_subtype(right_star_type, left_type): return False @@ -435,29 +444,23 @@ def is_callable_subtype(left: CallableType, right: CallableType, def is_left_more_general( - left_name: Optional[str], - left_pos: Optional[int], - left_type: Type, - left_required: bool, - right_name: Optional[str], - right_pos: Optional[int], - right_type: Type, - right_required: bool, + left: FormalArgument, + right: FormalArgument, ignore_pos_arg_names: bool) -> bool: # If right has a specific name it wants this argument to be, left must # have the same. - if right_name is not None and left_name != right_name: + if right.name is not None and left.name != right.name: # But pay attention to whether we're ignoring positional arg names - if not ignore_pos_arg_names or right_pos is None: + if not ignore_pos_arg_names or right.pos is None: return False # If right is at a specific position, left must have the same: - if right_pos is not None and left_pos != right_pos: + if right.pos is not None and left.pos != right.pos: return False # Left must have a more general type - if not is_subtype(right_type, left_type): + if not is_subtype(right.typ, left.typ): return False # If right's argument is optional, left's must also be. - if not right_required and left_required: + if not right.required and left.required: return False return True diff --git a/mypy/types.py b/mypy/types.py index dffe9101a02f..01695f6f7c41 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -5,6 +5,7 @@ from collections import OrderedDict from typing import ( Any, TypeVar, Dict, List, Tuple, cast, Generic, Set, Sequence, Optional, Union, Iterable, + NamedTuple, ) import mypy.nodes @@ -542,6 +543,13 @@ def deserialize(cls, data: JsonDict) -> 'FunctionLike': _dummy = object() # type: Any +FormalArgument = NamedTuple('FormalArgument', [ + ('name', str), + ('pos', int), + ('typ', Type), + ('required', bool)]) + + class CallableType(FunctionLike): """Type of a non-overloaded callable object (function).""" @@ -662,11 +670,7 @@ def max_fixed_args(self) -> int: n -= 1 return n - def argument_by_name(self, name: str) -> Optional[Tuple[ - Optional[str], - Optional[int], - Type, - bool]]: + def argument_by_name(self, name: str) -> Optional[FormalArgument]: seen_star = False star2_type = None # type: Optional[Type] for i, (arg_name, kind, typ) in enumerate( @@ -681,16 +685,12 @@ def argument_by_name(self, name: str) -> Optional[Tuple[ continue if arg_name == name: position = None if seen_star else i - return name, position, typ, kind in (ARG_POS, ARG_NAMED) + return FormalArgument(name, position, typ, kind in (ARG_POS, ARG_NAMED)) if star2_type is not None: - return name, None, star2_type, False + return FormalArgument(name, None, star2_type, False) return None - def argument_by_position(self, position: int) -> Optional[Tuple[ - Optional[str], - Optional[int], - Type, - bool]]: + def argument_by_position(self, position: int) -> Optional[FormalArgument]: if self.is_var_arg: for kind, typ in zip(self.arg_kinds, self.arg_types): if kind == ARG_STAR: @@ -698,7 +698,7 @@ def argument_by_position(self, position: int) -> Optional[Tuple[ break if position >= len(self.arg_names): if self.is_var_arg: - return None, position, star_type, False + return FormalArgument(None, position, star_type, False) else: return None name, kind, typ = ( @@ -707,10 +707,10 @@ def argument_by_position(self, position: int) -> Optional[Tuple[ self.arg_types[position], ) if kind in (ARG_POS, ARG_OPT): - return name, position, typ, kind == ARG_POS + return FormalArgument(name, position, typ, kind == ARG_POS) else: if self.is_var_arg: - return None, position, star_type, False + return FormalArgument(None, position, star_type, False) else: return None From 8bf9c533921cfb25a4b9aa69ced338d8270c7e07 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 2 Dec 2016 18:10:26 -0800 Subject: [PATCH 04/18] Stop checking argument names for multiple base classes --- mypy/checker.py | 2 +- mypy/subtypes.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index f13d939406e1..64cf094558f9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1043,7 +1043,7 @@ def check_compatibility(self, name: str, base1: TypeInfo, # Method override first_sig = bind_self(first_type) second_sig = bind_self(second_type) - ok = is_subtype(first_sig, second_sig) + ok = is_subtype(first_sig, second_sig, ignore_pos_arg_names=True) elif first_type and second_type: ok = is_equivalent(first_type, second_type) else: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index d9b4a5f346e5..f1785dc9e3ef 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -64,8 +64,11 @@ def ignore_tvars(s: Type, t: Type, v: int) -> bool: def is_equivalent(a: Type, b: Type, - type_parameter_checker: TypeParameterChecker = check_type_parameter) -> bool: - return is_subtype(a, b, type_parameter_checker) and is_subtype(b, a, type_parameter_checker) + type_parameter_checker: TypeParameterChecker = check_type_parameter, + *, ignore_pos_arg_names=False) -> bool: + return ( + is_subtype(a, b, type_parameter_checker, ignore_pos_arg_names=ignore_pos_arg_names) + and is_subtype(b, a, type_parameter_checker, ignore_pos_arg_names=ignore_pos_arg_names)) def satisfies_upper_bound(a: Type, upper_bound: Type) -> bool: From 1e4d46786b15057760a171f7a8252b6e9db1a70d Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 9 Dec 2016 16:27:20 -0800 Subject: [PATCH 05/18] Elide __foo argument names from funcs where args are not typed --- mypy/types.py | 3 ++- test-data/unit/check-functions.test | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/mypy/types.py b/mypy/types.py index 01695f6f7c41..35583f6f981a 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -15,6 +15,7 @@ ) from mypy import experiments +from mypy.sharedparse import argument_elide_name T = TypeVar('T') @@ -1699,7 +1700,7 @@ def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike return CallableType( [AnyType()] * len(fdef.arg_names), fdef.arg_kinds, - fdef.arg_names, + [None if argument_elide_name(n) else n for n in fdef.arg_names], AnyType(), fallback, name, diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index a3e0470a5907..22781c50df1a 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -132,6 +132,28 @@ ee_def = specific_1 # E: Incompatible types in assignment (expression has type C [builtins fixtures/dict.pyi] +[case testLackOfNamesImplicitAny] + +def f(__a): pass +def g(a): pass + +ff = f +gg = g +ff = g +gg = f # E: Incompatible types in assignment (expression has type Callable[[Any], Any], variable has type Callable[[Arg('a', Any, False)], Any]) + + +[case testLackOfNamesImplicitAnyFastparse] +# flags: --fast-parser + +def f(__a): pass +def g(a): pass + +ff = f +gg = g +ff = g +gg = f # E: Incompatible types in assignment (expression has type Callable[[Any], Any], variable has type Callable[[Arg('a', Any, False)], Any]) + [case testLackOfNames] def f(__a: int, __b: str) -> None: pass def g(a: int, b: str) -> None: pass From c42bd8609e0c89b0600f4f2f24e34a3ed56a1d0a Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 9 Dec 2016 16:28:03 -0800 Subject: [PATCH 06/18] Cosmetic --- mypy/subtypes.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index f1785dc9e3ef..4e7c475792c2 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -367,8 +367,8 @@ def is_callable_subtype(left: CallableType, right: CallableType, # sure left has its own infinite set of optional named arguments. if not left.is_kw_arg: return False - left_names = set([name for name in left.arg_names if name is not None]) - right_names = set([name for name in right.arg_names if name is not None]) + left_names = { name for name in left.arg_names if name is not None } + right_names = { name for name in right.arg_names if name is not None } left_only_names = left_names - right_names for name in left_only_names: left_by_name = left.argument_by_name(name) @@ -401,7 +401,11 @@ def is_callable_subtype(left: CallableType, right: CallableType, and left_by_position is not None and left_by_name != left_by_position): # If we're dealing with an optional pos-only and an optional - # name-only arg, merge them. + # name-only arg, merge them. This is the case for all functions + # taking both *args and **args, or a pair of functions like so: + + # def right(a: int = ...) -> None: ... + # def left(__a: int = ..., *, a: int = ...) -> None: ... if (left_by_position.required is False and left_by_name.required is False and left_by_position.name is None and left_by_name.pos is None and is_equivalent(left_by_position.typ, left_by_name.typ)): From c8d4458c34419f888fe06ecadc9abe4d2ae66b9d Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 9 Dec 2016 16:51:25 -0800 Subject: [PATCH 07/18] Make the text descriptions of args more concise --- mypy/messages.py | 15 ++++++++------- test-data/unit/check-functions.test | 22 +++++++++++----------- test-data/unit/check-varargs.test | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 50504a868ae1..44db0599a620 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -80,8 +80,8 @@ ARG_CONSTRUCTOR_NAMES = { ARG_POS: "Arg", ARG_OPT: "DefaultArg", - ARG_NAMED: "Arg", - ARG_NAMED_OPT: "DefaultArg", + ARG_NAMED: "NamedArg", + ARG_NAMED_OPT: "DefaultNamedArg", ARG_STAR: "StarArg", ARG_STAR2: "KwArg", } @@ -188,7 +188,9 @@ def format(self, typ: Type, verbosity: int = 0) -> str: arg_strings = [] for arg_name, arg_type, arg_kind in zip( func.arg_names, func.arg_types, func.arg_kinds): - if arg_kind == ARG_POS and arg_name is None or verbosity == 0: + if ( arg_kind == ARG_POS and arg_name is None or + verbosity == 0 and arg_kind in (ARG_POS, ARG_OPT) ): + arg_strings.append( strip_quotes( self.format( @@ -199,13 +201,12 @@ def format(self, typ: Type, verbosity: int = 0) -> str: if arg_kind in (ARG_STAR, ARG_STAR2): arg_strings.append("{}({})".format( constructor, - arg_name)) + strip_quotes(self.format(arg_type)))) else: - arg_strings.append("{}('{}', {}, {})".format( + arg_strings.append("{}('{}', {})".format( constructor, arg_name, - strip_quotes(self.format(arg_type)), - arg_kind in (ARG_NAMED, ARG_NAMED_OPT))) + strip_quotes(self.format(arg_type)))) return 'Callable[[{}], {}]'.format(", ".join(arg_strings), return_type) else: diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 22781c50df1a..895efc12469f 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -99,10 +99,10 @@ hh = h ff = gg ff_nonames = ff ff_nonames = f_nonames # reset -ff = ff_nonames # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[Arg('a', int, False), Arg('b', str, False)], None]) +ff = ff_nonames # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[Arg('a', int), Arg('b', str)], None]) ff = f # reset -gg = ff # E: Incompatible types in assignment (expression has type Callable[[Arg('a', int, False), Arg('b', str, False)], None], variable has type Callable[[Arg('a', int, False), DefaultArg('b', str, False)], None]) -gg = hh # E: Incompatible types in assignment (expression has type Callable[[Arg('aa', int, False), DefaultArg('b', str, False)], None], variable has type Callable[[Arg('a', int, False), DefaultArg('b', str, False)], None]) +gg = ff # E: Incompatible types in assignment (expression has type Callable[[Arg('a', int), Arg('b', str)], None], variable has type Callable[[Arg('a', int), DefaultArg('b', str)], None]) +gg = hh # E: Incompatible types in assignment (expression has type Callable[[Arg('aa', int), DefaultArg('b', str)], None], variable has type Callable[[Arg('a', int), DefaultArg('b', str)], None]) [case testSubtypingFunctionsArgsKwargs] from typing import Any, Callable @@ -127,8 +127,8 @@ ee_def = everything ee_var = everything ee_var = everywhere -ee_var = specific_1 # The difference between Callable[[...], blah] and one with a *args: Any, **kwargs: Any is that the ... goes loosely both ways. -ee_def = specific_1 # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[Any, Any], None]) +ee_var = specific_1 # The difference between Callable[..., blah] and one with a *args: Any, **kwargs: Any is that the ... goes loosely both ways. +ee_def = specific_1 # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[StarArg(Any), KwArg(Any)], None]) [builtins fixtures/dict.pyi] @@ -140,7 +140,7 @@ def g(a): pass ff = f gg = g ff = g -gg = f # E: Incompatible types in assignment (expression has type Callable[[Any], Any], variable has type Callable[[Arg('a', Any, False)], Any]) +gg = f # E: Incompatible types in assignment (expression has type Callable[[Any], Any], variable has type Callable[[Arg('a', Any)], Any]) [case testLackOfNamesImplicitAnyFastparse] @@ -152,7 +152,7 @@ def g(a): pass ff = f gg = g ff = g -gg = f # E: Incompatible types in assignment (expression has type Callable[[Any], Any], variable has type Callable[[Arg('a', Any, False)], Any]) +gg = f # E: Incompatible types in assignment (expression has type Callable[[Any], Any], variable has type Callable[[Arg('a', Any)], Any]) [case testLackOfNames] def f(__a: int, __b: str) -> None: pass @@ -162,7 +162,7 @@ ff = f gg = g ff = g -gg = f # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[Arg('a', int, False), Arg('b', str, False)], None]) +gg = f # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[Arg('a', int), Arg('b', str)], None]) [case testLackOfNamesFastparse] # flags: --fast-parser @@ -174,7 +174,7 @@ ff = f gg = g ff = g -gg = f # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[Arg('a', int, False), Arg('b', str, False)], None]) +gg = f # E: Incompatible types in assignment (expression has type Callable[[int, str], None], variable has type Callable[[Arg('a', int), Arg('b', str)], None]) [case testFunctionTypeCompatibilityWithOtherTypes] from typing import Callable @@ -1412,7 +1412,7 @@ def g4(*, y: int) -> str: pass f(g1) f(g2) f(g3) -f(g4) # E: Argument 1 to "f" has incompatible type Callable[[int], str]; expected Callable[..., int] +f(g4) # E: Argument 1 to "f" has incompatible type Callable[[NamedArg('y', int)], str]; expected Callable[..., int] [case testCallableWithArbitraryArgsSubtypingWithGenericFunc] from typing import Callable, TypeVar @@ -1540,7 +1540,7 @@ def g(x, y): pass def h(x): pass def j(y): pass f = h -f = j # E: Incompatible types in assignment (expression has type Callable[[Arg('y', Any, False)], Any], variable has type Callable[[Arg('x', Any, False)], Any]) +f = j # E: Incompatible types in assignment (expression has type Callable[[Arg('y', Any)], Any], variable has type Callable[[Arg('x', Any)], Any]) f = g # E: Incompatible types in assignment (expression has type Callable[[Any, Any], Any], variable has type Callable[[Any], Any]) [case testRedefineFunction2] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index e59a332b00f6..89120bffe153 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -572,7 +572,7 @@ x = None # type: Callable[[int], None] def f(*x: int) -> None: pass def g(*x: str) -> None: pass x = f -x = g # E: Incompatible types in assignment (expression has type Callable[[str], None], variable has type Callable[[int], None]) +x = g # E: Incompatible types in assignment (expression has type Callable[[StarArg(str)], None], variable has type Callable[[int], None]) [builtins fixtures/list.pyi] [out] From d7df8cd55a6a2b541da0da2354a959403e449a1c Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 9 Dec 2016 17:24:27 -0800 Subject: [PATCH 08/18] Account for one left argument with two corresponding right args --- mypy/subtypes.py | 55 +++++++++++++++++++---------- test-data/unit/check-functions.test | 11 ++++++ 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 4e7c475792c2..5353a04a68da 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -424,29 +424,48 @@ def is_callable_subtype(left: CallableType, right: CallableType, if not is_left_more_general(left_arg, right_arg, ignore_pos_arg_names): return False + done_with_positional = False for i in range(len(left.arg_types)): - left_name = left.arg_names[i] left_kind = left.arg_kinds[i] - left_type = left.arg_types[i] - # All *required* left-hand arguments must have a corresponding - # right-hand argument. The relationship between the two is handled in - # the loop above. - - if left_kind == ARG_POS and right.argument_by_position(i) is None: - if left_name is None: - return False - if right.argument_by_name(left_name) is None: - return False - elif left_kind == ARG_NAMED: - if right.argument_by_name(left_name) is None: - return False - # Also check that *args and **kwargs types match in this loop - elif left_kind == ARG_STAR: - if right_star_type is not None and not is_subtype(right_star_type, left_type): + if left_kind in (ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT): + done_with_positional = True + left_arg = FormalArgument( + left.arg_names[i], + None if done_with_positional else i, + left.arg_types[i], + left_kind in (ARG_POS, ARG_NAMED)) + + # Check that *args and **kwargs types match in this loop + if left_kind == ARG_STAR: + if right_star_type is not None and not is_subtype(right_star_type, left_arg.typ): return False + continue elif left_kind == ARG_STAR2: - if right_star2_type is not None and not is_subtype(right_star2_type, left_type): + if right_star2_type is not None and not is_subtype(right_star2_type, left_arg.typ): return False + continue + + right_by_name = ( right.argument_by_name(left_arg.name) + if left_arg.name is not None + else None ) + + right_by_pos = ( right.argument_by_position(left_arg.pos) + if left_arg.pos is not None + else None ) + + # If the left hand argument corresponds to two right-hand arguments, + # neither of them can be required. + if (right_by_name is not None + and right_by_pos is not None + and right_by_name != right_by_pos + and (right_by_pos.required or right_by_name.required)): + return False + + # All *required* left-hand arguments must have a corresponding + # right-hand argument. + if left_arg.required and right_by_pos is None and right_by_name is None: + return False + return True diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 895efc12469f..7580d78f36b7 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -82,6 +82,17 @@ f = f g = g h = h +[case testSubtypingFunctionsDoubleCorrespondence] + +def l(x) -> None: ... +def r(__, *, x) -> None: ... +r = l # E: Incompatible types in assignment (expression has type Callable[[Any], None], variable has type Callable[[Any, NamedArg('x', Any)], None]) + +[case testSubtypingFunctionsRequiredLeftArgNotPresent] + +def l(x, y) -> None: ... +def r(x) -> None: ... +r = l # E: Incompatible types in assignment (expression has type Callable[[Any, Any], None], variable has type Callable[[Any], None]) [case testSubtypingFunctionsDefaultsNames] from typing import Callable From d811a827349466b9045dc021c1a258a6e25cfd2c Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 9 Dec 2016 17:26:47 -0800 Subject: [PATCH 09/18] cosmetic --- mypy/subtypes.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 5353a04a68da..5612c1945a3b 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -353,7 +353,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, # This fetches the synthetic argument that's from the *args right_by_position = right.argument_by_position(j) assert right_by_position is not None - if not is_left_more_general(left_by_position, right_by_position, + if not are_args_compatible(left_by_position, right_by_position, ignore_pos_arg_names): return False j += 1 @@ -376,7 +376,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, # This fetches the synthetic argument that's from the **kwargs right_by_name = right.argument_by_name(name) assert right_by_name is not None - if not is_left_more_general(left_by_name, right_by_name, + if not are_args_compatible(left_by_name, right_by_name, ignore_pos_arg_names): return False continue @@ -421,7 +421,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, assert left_arg is not None - if not is_left_more_general(left_arg, right_arg, ignore_pos_arg_names): + if not are_args_compatible(left_arg, right_arg, ignore_pos_arg_names): return False done_with_positional = False @@ -462,14 +462,14 @@ def is_callable_subtype(left: CallableType, right: CallableType, return False # All *required* left-hand arguments must have a corresponding - # right-hand argument. + # right-hand argument. Optional args it does not matter. if left_arg.required and right_by_pos is None and right_by_name is None: return False return True -def is_left_more_general( +def are_args_compatible( left: FormalArgument, right: FormalArgument, ignore_pos_arg_names: bool) -> bool: From 95d1c5888bf8cd619dad7ada8af3bf593ed00d1e Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 9 Dec 2016 17:40:01 -0800 Subject: [PATCH 10/18] cosmetic --- mypy/messages.py | 4 ++-- mypy/subtypes.py | 20 ++++++++++---------- test-data/unit/pythoneval.test | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 44db0599a620..fb5923dc52c4 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -188,8 +188,8 @@ def format(self, typ: Type, verbosity: int = 0) -> str: arg_strings = [] for arg_name, arg_type, arg_kind in zip( func.arg_names, func.arg_types, func.arg_kinds): - if ( arg_kind == ARG_POS and arg_name is None or - verbosity == 0 and arg_kind in (ARG_POS, ARG_OPT) ): + if (arg_kind == ARG_POS and arg_name is None + or verbosity == 0 and arg_kind in (ARG_POS, ARG_OPT)): arg_strings.append( strip_quotes( diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 5612c1945a3b..307d3ff6451b 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -354,7 +354,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, right_by_position = right.argument_by_position(j) assert right_by_position is not None if not are_args_compatible(left_by_position, right_by_position, - ignore_pos_arg_names): + ignore_pos_arg_names): return False j += 1 continue @@ -367,8 +367,8 @@ def is_callable_subtype(left: CallableType, right: CallableType, # sure left has its own infinite set of optional named arguments. if not left.is_kw_arg: return False - left_names = { name for name in left.arg_names if name is not None } - right_names = { name for name in right.arg_names if name is not None } + left_names = {name for name in left.arg_names if name is not None} + right_names = {name for name in right.arg_names if name is not None} left_only_names = left_names - right_names for name in left_only_names: left_by_name = left.argument_by_name(name) @@ -377,7 +377,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, right_by_name = right.argument_by_name(name) assert right_by_name is not None if not are_args_compatible(left_by_name, right_by_name, - ignore_pos_arg_names): + ignore_pos_arg_names): return False continue @@ -445,13 +445,13 @@ def is_callable_subtype(left: CallableType, right: CallableType, return False continue - right_by_name = ( right.argument_by_name(left_arg.name) - if left_arg.name is not None - else None ) + right_by_name = (right.argument_by_name(left_arg.name) + if left_arg.name is not None + else None) - right_by_pos = ( right.argument_by_position(left_arg.pos) - if left_arg.pos is not None - else None ) + right_by_pos = (right.argument_by_position(left_arg.pos) + if left_arg.pos is not None + else None) # If the left hand argument corresponds to two right-hand arguments, # neither of them can be required. diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 6bf0e97925ae..1887177d3320 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -755,7 +755,7 @@ def f(*args: str) -> str: return args[0] map(f, ['x']) map(f, [1]) [out] -_program.py:4: error: Argument 1 to "map" has incompatible type Callable[[str], str]; expected Callable[[int], str] +_program.py:4: error: Argument 1 to "map" has incompatible type Callable[[StarArg(str)], str]; expected Callable[[int], str] [case testMapStr] import typing From d75249975f0f166be3b727c15018c06db35de5e5 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 9 Dec 2016 17:53:34 -0800 Subject: [PATCH 11/18] Remove `In class foo` --- test-data/unit/check-functions.test | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 7580d78f36b7..a97a96ef50a6 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -33,9 +33,6 @@ class B(A): class C(A): def f(self, *, b: int, a: str) -> None: pass # E: Signature of "f" incompatible with supertype "A" -[out] -main: note: In class "C": - [case testPositionalOverridingArgumentNameInsensitivity] import typing @@ -48,8 +45,6 @@ class B(A): class C(A): def f(self, foo: int, bar: str) -> None: pass -[out] -main: note: In class "B": [case testPositionalOverridingArgumentNamesCheckedWhenMismatchingPos] import typing @@ -60,8 +55,6 @@ class A(object): class B(A): def f(self, b: int, a: str) -> None: pass # E: Signature of "f" incompatible with supertype "A" -[out] -main: note: In class "B": [case testSubtypingFunctionTypes] from typing import Callable From 499e1d2f735668e276ddaa3083785cce5139bfbb Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Wed, 21 Dec 2016 14:48:15 -0800 Subject: [PATCH 12/18] Factor out corresponding_argument method --- mypy/subtypes.py | 39 ++------------------------------------- mypy/types.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 37 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 307d3ff6451b..6946d4c92e38 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -381,46 +381,11 @@ def is_callable_subtype(left: CallableType, right: CallableType, return False continue - # Get possible corresponding arguments by name and by position. - if not done_with_positional: - left_by_position = left.argument_by_position(i) - else: - left_by_position = None - if right_arg.name is not None: - left_by_name = left.argument_by_name(right_arg.name) - else: - left_by_name = None - # Left must have some kind of corresponding argument. - if left_by_name is None and left_by_position is None: + left_arg = left.corresponding_argument(right_arg) + if left_arg is None: return False - left_arg = None # type: Optional[FormalArgument] - # If there's ambiguity as to how to match up this argument, it's not a subtype - if (left_by_name is not None - and left_by_position is not None - and left_by_name != left_by_position): - # If we're dealing with an optional pos-only and an optional - # name-only arg, merge them. This is the case for all functions - # taking both *args and **args, or a pair of functions like so: - - # def right(a: int = ...) -> None: ... - # def left(__a: int = ..., *, a: int = ...) -> None: ... - if (left_by_position.required is False and left_by_name.required is False - and left_by_position.name is None and left_by_name.pos is None - and is_equivalent(left_by_position.typ, left_by_name.typ)): - left_arg = FormalArgument( - left_by_name.name, - left_by_position.pos, - left_by_position.typ, - required=False) - else: - return False - else: - left_arg = left_by_name if left_by_name is not None else left_by_position - - assert left_arg is not None - if not are_args_compatible(left_arg, right_arg, ignore_pos_arg_names): return False diff --git a/mypy/types.py b/mypy/types.py index 35583f6f981a..75462f4c87d0 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -671,7 +671,33 @@ def max_fixed_args(self) -> int: n -= 1 return n + def corresponding_argument(self, model: FormalArgument) -> Optional[FormalArgument]: + """Return the argument in this function that corresponds to `model`""" + + by_name = self.argument_by_name(model.name) + by_pos = self.argument_by_position(model.pos) + if by_name is None and by_pos is None: + return None + if by_name is not None and by_pos is not None: + if by_name == by_pos: + return by_name + # If we're dealing with an optional pos-only and an optional + # name-only arg, merge them. This is the case for all functions + # taking both *args and **args, or a pair of functions like so: + + # def right(a: int = ...) -> None: ... + # def left(__a: int = ..., *, a: int = ...) -> None: ... + from mypy.subtypes import is_equivalent + if (not (by_name.required or by_pos.required) + and by_pos.name is None + and by_name.pos is None + and is_equivalent(by_name.typ, by_pos.typ)): + return FormalArgument(by_name.name, by_pos.pos, by_name.typ, False) + return by_name if by_name is not None else by_pos + def argument_by_name(self, name: str) -> Optional[FormalArgument]: + if name is None: + return None seen_star = False star2_type = None # type: Optional[Type] for i, (arg_name, kind, typ) in enumerate( @@ -692,6 +718,8 @@ def argument_by_name(self, name: str) -> Optional[FormalArgument]: return None def argument_by_position(self, position: int) -> Optional[FormalArgument]: + if position is None: + return None if self.is_var_arg: for kind, typ in zip(self.arg_kinds, self.arg_types): if kind == ARG_STAR: From 7617eac45a502a9c84841fdde99739098f64f561 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Wed, 21 Dec 2016 15:05:09 -0800 Subject: [PATCH 13/18] Optional for the name and pos --- mypy/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/types.py b/mypy/types.py index 75462f4c87d0..e852f96e5730 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -545,8 +545,8 @@ def deserialize(cls, data: JsonDict) -> 'FunctionLike': FormalArgument = NamedTuple('FormalArgument', [ - ('name', str), - ('pos', int), + ('name', Optional[str]), + ('pos', Optional[int]), ('typ', Type), ('required', bool)]) From 77c8f73e3db6fb4f003fe2983d2867aaab5778b3 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 22 Dec 2016 07:38:43 -0800 Subject: [PATCH 14/18] typo --- mypy/subtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 6946d4c92e38..bc933d5b84f8 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -322,7 +322,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, # used for in practice. # Every argument in R must have a corresponding argument in L, and every - # required argument in L must have a corresponding arugment in R. + # required argument in L must have a corresponding argument in R. done_with_positional = False for i in range(len(right.arg_types)): right_kind = right.arg_kinds[i] From 62fd86816cef738d15f2e1bc6ab7ac6be6dfb461 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 22 Dec 2016 08:14:38 -0800 Subject: [PATCH 15/18] Ignore implicitly typed function arg names --- mypy/subtypes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index bc933d5b84f8..43f28c18d029 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -275,6 +275,11 @@ def is_callable_subtype(left: CallableType, right: CallableType, ignore_return: bool = False, ignore_pos_arg_names: bool = False) -> bool: """Is left a subtype of right?""" + + # If either function is implicitly typed, ignore positional arg names too + if left.implicit or right.implicit: + ignore_pos_arg_names = True + # Non-type cannot be a subtype of type. if right.is_type_obj() and not left.is_type_obj(): return False From 5c6d5749e9dfde8399875b9ee245ce7f42bf7a6a Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 22 Dec 2016 08:30:37 -0800 Subject: [PATCH 16/18] Test for ignoring arg names for implicit defs --- test-data/unit/check-functions.test | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index a97a96ef50a6..6e249a8c58c8 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -87,6 +87,17 @@ def l(x, y) -> None: ... def r(x) -> None: ... r = l # E: Incompatible types in assignment (expression has type Callable[[Any, Any], None], variable has type Callable[[Any], None]) +[case testSubtypingFunctionsImplicitNames] + +def f(a, b): pass +def g(c: Any, d: Any) -> Any: pass + +ff = f +gg = g + +gg = f +ff = g + [case testSubtypingFunctionsDefaultsNames] from typing import Callable @@ -1539,10 +1550,10 @@ class A(Generic[t]): [case testRedefineFunction] -def f(x): pass +def f(x) -> Any: pass def g(x, y): pass def h(x): pass -def j(y): pass +def j(y) -> Any: pass f = h f = j # E: Incompatible types in assignment (expression has type Callable[[Arg('y', Any)], Any], variable has type Callable[[Arg('x', Any)], Any]) f = g # E: Incompatible types in assignment (expression has type Callable[[Any, Any], Any], variable has type Callable[[Any], Any]) From 4397926ef304f42474c4e8a5d642ced2c9b0c678 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 22 Dec 2016 08:40:24 -0800 Subject: [PATCH 17/18] Fix tests --- test-data/unit/check-functions.test | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 6e249a8c58c8..35bb7eda2be4 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -149,8 +149,8 @@ ee_def = specific_1 # E: Incompatible types in assignment (expression has type C [case testLackOfNamesImplicitAny] -def f(__a): pass -def g(a): pass +def f(__a) -> Any: pass +def g(a) -> Any: pass ff = f gg = g @@ -161,8 +161,8 @@ gg = f # E: Incompatible types in assignment (expression has type Callable[[Any] [case testLackOfNamesImplicitAnyFastparse] # flags: --fast-parser -def f(__a): pass -def g(a): pass +def f(__a) -> Any: pass +def g(a) -> Any: pass ff = f gg = g From 2a07da84f6cbe9dafd0ab916c42a63fef27c8a7b Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 22 Dec 2016 08:54:45 -0800 Subject: [PATCH 18/18] Delete tests for implicit callable lacking names as they make no sense now --- test-data/unit/check-functions.test | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 35bb7eda2be4..31a61688eeee 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -147,28 +147,6 @@ ee_def = specific_1 # E: Incompatible types in assignment (expression has type C [builtins fixtures/dict.pyi] -[case testLackOfNamesImplicitAny] - -def f(__a) -> Any: pass -def g(a) -> Any: pass - -ff = f -gg = g -ff = g -gg = f # E: Incompatible types in assignment (expression has type Callable[[Any], Any], variable has type Callable[[Arg('a', Any)], Any]) - - -[case testLackOfNamesImplicitAnyFastparse] -# flags: --fast-parser - -def f(__a) -> Any: pass -def g(a) -> Any: pass - -ff = f -gg = g -ff = g -gg = f # E: Incompatible types in assignment (expression has type Callable[[Any], Any], variable has type Callable[[Arg('a', Any)], Any]) - [case testLackOfNames] def f(__a: int, __b: str) -> None: pass def g(a: int, b: str) -> None: pass