Skip to content

Commit

Permalink
Function compatibility rewrite (#2521)
Browse files Browse the repository at this point in the history
* 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

* Rename argument shorter

* Explain what i am doing better

* Stop checking argument names for multiple base classes

* Elide __foo argument names from funcs where args are not typed

* Cosmetic

* Make the text descriptions of args more concise

* Account for one left argument with two corresponding right args

* cosmetic

* cosmetic

* Remove `In class foo`

* Factor out corresponding_argument method

* Optional for the name and pos

* typo

* Ignore implicitly typed function arg names

* Test for ignoring arg names for implicit defs

* Fix tests

* Delete tests for implicit callable lacking names as they make no sense now
  • Loading branch information
sixolet authored and JukkaL committed Dec 22, 2016
1 parent ca2d2c6 commit 507c3c2
Show file tree
Hide file tree
Showing 9 changed files with 445 additions and 68 deletions.
10 changes: 5 additions & 5 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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_pos_arg_names=True):
fail = True
elif (not isinstance(original, Overloaded) and
isinstance(override, Overloaded) and
Expand Down Expand Up @@ -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:
Expand Down
37 changes: 34 additions & 3 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)


Expand Down Expand Up @@ -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: "NamedArg",
ARG_NAMED_OPT: "DefaultNamedArg",
ARG_STAR: "StarArg",
ARG_STAR2: "KwArg",
}


class MessageBuilder:
"""Helper class for reporting type checker error messages with parameters.
Expand Down Expand Up @@ -176,8 +185,30 @@ 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 and arg_kind in (ARG_POS, ARG_OPT)):

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,
strip_quotes(self.format(arg_type))))
else:
arg_strings.append("{}('{}', {})".format(
constructor,
arg_name,
strip_quotes(self.format(arg_type))))

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
Expand Down
Loading

0 comments on commit 507c3c2

Please sign in to comment.