Skip to content

Commit 507c3c2

Browse files
sixoletJukkaL
authored andcommitted
Function compatibility rewrite (#2521)
* 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
1 parent ca2d2c6 commit 507c3c2

9 files changed

+445
-68
lines changed

mypy/checker.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
from mypy.checkmember import map_type_from_supertype, bind_self, erase_to_bound
4141
from mypy import messages
4242
from mypy.subtypes import (
43-
is_subtype, is_equivalent, is_proper_subtype, is_more_precise, restrict_subtype_away,
44-
is_subtype_ignoring_tvars
43+
is_subtype, is_equivalent, is_proper_subtype, is_more_precise,
44+
restrict_subtype_away, is_subtype_ignoring_tvars
4545
)
4646
from mypy.maptype import map_instance_to_supertype
4747
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:
835835
def check_getattr_method(self, typ: CallableType, context: Context) -> None:
836836
method_type = CallableType([AnyType(), self.named_type('builtins.str')],
837837
[nodes.ARG_POS, nodes.ARG_POS],
838-
[None],
838+
[None, None],
839839
AnyType(),
840840
self.named_type('builtins.function'))
841841
if not is_subtype(typ, method_type):
@@ -936,7 +936,7 @@ def check_override(self, override: FunctionLike, original: FunctionLike,
936936
"""
937937
# Use boolean variable to clarify code.
938938
fail = False
939-
if not is_subtype(override, original):
939+
if not is_subtype(override, original, ignore_pos_arg_names=True):
940940
fail = True
941941
elif (not isinstance(original, Overloaded) and
942942
isinstance(override, Overloaded) and
@@ -1043,7 +1043,7 @@ def check_compatibility(self, name: str, base1: TypeInfo,
10431043
# Method override
10441044
first_sig = bind_self(first_type)
10451045
second_sig = bind_self(second_type)
1046-
ok = is_subtype(first_sig, second_sig)
1046+
ok = is_subtype(first_sig, second_sig, ignore_pos_arg_names=True)
10471047
elif first_type and second_type:
10481048
ok = is_equivalent(first_type, second_type)
10491049
else:

mypy/messages.py

+34-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
)
1616
from mypy.nodes import (
1717
TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases,
18-
ARG_STAR, ARG_STAR2
18+
ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2
1919
)
2020

2121

@@ -77,6 +77,15 @@
7777
TYPEDDICT_ITEM_NAME_MUST_BE_STRING_LITERAL = \
7878
'Expected TypedDict item name to be string literal'
7979

80+
ARG_CONSTRUCTOR_NAMES = {
81+
ARG_POS: "Arg",
82+
ARG_OPT: "DefaultArg",
83+
ARG_NAMED: "NamedArg",
84+
ARG_NAMED_OPT: "DefaultNamedArg",
85+
ARG_STAR: "StarArg",
86+
ARG_STAR2: "KwArg",
87+
}
88+
8089

8190
class MessageBuilder:
8291
"""Helper class for reporting type checker error messages with parameters.
@@ -176,8 +185,30 @@ def format(self, typ: Type, verbosity: int = 0) -> str:
176185
return_type = strip_quotes(self.format(func.ret_type))
177186
if func.is_ellipsis_args:
178187
return 'Callable[..., {}]'.format(return_type)
179-
arg_types = [strip_quotes(self.format(t)) for t in func.arg_types]
180-
return 'Callable[[{}], {}]'.format(", ".join(arg_types), return_type)
188+
arg_strings = []
189+
for arg_name, arg_type, arg_kind in zip(
190+
func.arg_names, func.arg_types, func.arg_kinds):
191+
if (arg_kind == ARG_POS and arg_name is None
192+
or verbosity == 0 and arg_kind in (ARG_POS, ARG_OPT)):
193+
194+
arg_strings.append(
195+
strip_quotes(
196+
self.format(
197+
arg_type,
198+
verbosity = max(verbosity - 1, 0))))
199+
else:
200+
constructor = ARG_CONSTRUCTOR_NAMES[arg_kind]
201+
if arg_kind in (ARG_STAR, ARG_STAR2):
202+
arg_strings.append("{}({})".format(
203+
constructor,
204+
strip_quotes(self.format(arg_type))))
205+
else:
206+
arg_strings.append("{}('{}', {})".format(
207+
constructor,
208+
arg_name,
209+
strip_quotes(self.format(arg_type))))
210+
211+
return 'Callable[[{}], {}]'.format(", ".join(arg_strings), return_type)
181212
else:
182213
# Use a simple representation for function types; proper
183214
# function types may result in long and difficult-to-read

0 commit comments

Comments
 (0)