Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List possible overload variants when none match #5296

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1217,12 +1217,19 @@ def check_overload_call(self,
if len(erased_targets) > 0:
# Pick the first plausible erased target as the fallback
# TODO: Adjust the error message here to make it clear there was no match.
# In order to do this, we need to find a clean way of associating
# a note with whatever error message 'self.check_call' will generate.
# In particular, the note's line and column numbers need to be the same
# as the error's.
target = erased_targets[0] # type: Type
else:
# There was no plausible match: give up
if not self.chk.should_suppress_optional_error(arg_types):
arg_messages.no_variant_matches_arguments(callee, arg_types, context)
target = AnyType(TypeOfAny.from_error)

if not self.chk.should_suppress_optional_error(arg_types):
arg_messages.no_variant_matches_arguments(
plausible_targets, callee, arg_types, context)

result = self.check_call(target, args, arg_kinds, context, arg_names,
arg_messages=arg_messages,
callable_name=callable_name,
Expand Down
57 changes: 49 additions & 8 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,10 @@ def deleted_as_lvalue(self, typ: DeletedType, context: Context) -> None:
s = " '{}'".format(typ.source)
self.fail('Assignment to variable{} outside except: block'.format(s), context)

def no_variant_matches_arguments(self, overload: Overloaded, arg_types: List[Type],
def no_variant_matches_arguments(self,
plausible_targets: List[CallableType],
overload: Overloaded,
arg_types: List[Type],
context: Context) -> None:
name = callable_name(overload)
if name:
Expand All @@ -768,6 +771,8 @@ def no_variant_matches_arguments(self, overload: Overloaded, arg_types: List[Typ
self.fail('No overload variant{} matches argument types {}'
.format(name_str, arg_types_str), context)

self.pretty_overload_matches(plausible_targets, overload, context, offset=2, max_items=2)

def wrong_number_values_to_unpack(self, provided: int, expected: int,
context: Context) -> None:
if provided < expected:
Expand Down Expand Up @@ -1233,14 +1238,49 @@ def report_protocol_problems(self, subtype: Union[Instance, TupleType, TypedDict
.format(supertype.type.name(), name), context)
self.print_more(conflict_flags, context, OFFSET, MAX_ITEMS)

def pretty_overload(self, tp: Overloaded, context: Context,
def pretty_overload(self, func: Overloaded, context: Context,
offset: int, max_items: int) -> None:
for item in tp.items()[:max_items]:
targets = func.items()
for item in targets[:max_items]:
self.note('@overload', context, offset=2 * offset)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extra line with @overload just doubles the verbosity without adding any information. I think it's okay to leave it out.

self.note(self.pretty_callable(item), context, offset=2 * offset)
if len(tp.items()) > max_items:
self.note('<{} more overload(s) not shown>'.format(len(tp.items()) - max_items),
context, offset=2 * offset)

max_available = len(targets)
shown = min(max_items, max_available)
if shown < max_available:
left = max_available - shown
msg = '<{} more overload{} not shown>'.format(left, plural_s(left))
self.note(msg, context, offset=2 * offset)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All changes in this function are unnecessary now, except plural_s usage.


def pretty_overload_matches(self,
targets: List[CallableType],
func: Overloaded,
context: Context,
offset: int,
max_items: int) -> None:
if not targets:
targets = func.items()

shown = min(max_items, len(targets))
max_matching = len(targets)
max_available = len(func.items())

self.note('Possible overload variant{}:'.format(plural_s(shown)), context)
for item in targets[:max_items]:
self.note(self.pretty_callable(item), context, offset=2 * offset)

assert shown <= max_matching <= max_available
if shown < max_matching <= max_available:
left = max_matching - shown
msg = '<{} more matching overload{} not shown, out of {} total overloads>'.format(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather say this as {} more matching and {} non-matching overload{} not shown. But this is still not perfect because the overloads aren't "matching" (as the error message above says). Anyway, I think it is not so important to delay merging this PR, so I will go ahead unless someone proposes better wording.

left, plural_s(left), max_available)
self.note(msg, context, offset=2 * offset)
elif shown == max_matching < max_available:
left = max_available - shown
msg = '<{} more non-matching overload{} not shown>'.format(left, plural_s(left))
self.note(msg, context, offset=2 * offset)
else:
assert shown == max_matching == max_available

def print_more(self, conflicts: Sequence[Any], context: Context,
offset: int, max_items: int) -> None:
Expand Down Expand Up @@ -1405,8 +1445,9 @@ def strip_quotes(s: str) -> str:
return s


def plural_s(s: Sequence[Any]) -> str:
if len(s) > 1:
def plural_s(s: Union[int, Sequence[Any]]) -> str:
count = s if isinstance(s, int) else len(s)
if count > 1:
return 's'
else:
return ''
Expand Down
10 changes: 8 additions & 2 deletions test-data/unit/check-abstract.test
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,10 @@ B().f(1)
a = B() # type: A
a.f(1)
a.f('')
a.f(B()) # E: No overload variant of "f" of "A" matches argument type "B"
a.f(B()) # E: No overload variant of "f" of "A" matches argument type "B" \
# N: Possible overload variants: \
# N: def f(self, x: int) -> int \
# N: def f(self, x: str) -> str

[case testOverloadedAbstractMethodWithAlternativeDecoratorOrder]
from foo import *
Expand All @@ -552,7 +555,10 @@ B().f(1)
a = B() # type: A
a.f(1)
a.f('')
a.f(B()) # E: No overload variant of "f" of "A" matches argument type "B"
a.f(B()) # E: No overload variant of "f" of "A" matches argument type "B" \
# N: Possible overload variants: \
# N: def f(self, x: int) -> int \
# N: def f(self, x: str) -> str

[case testOverloadedAbstractMethodVariantMissingDecorator1]
from foo import *
Expand Down
5 changes: 4 additions & 1 deletion test-data/unit/check-class-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,10 @@ class Overloader(NamedTuple):

reveal_type(Overloader(1).method('string')) # E: Revealed type is 'builtins.str'
reveal_type(Overloader(1).method(1)) # E: Revealed type is 'builtins.int'
Overloader(1).method(('tuple',)) # E: No overload variant of "method" of "Overloader" matches argument type "Tuple[str]"
Overloader(1).method(('tuple',)) # E: No overload variant of "method" of "Overloader" matches argument type "Tuple[str]" \
# N: Possible overload variants: \
# N: def method(self, y: str) -> str \
# N: def method(self, y: int) -> int

[case testNewNamedTupleMethodInheritance]
from typing import NamedTuple, TypeVar
Expand Down
38 changes: 33 additions & 5 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -1225,8 +1225,14 @@ class D:
[out]
main:5: error: Revealed type is 'Any'
main:5: error: No overload variant of "__get__" of "D" matches argument types "None", "Type[A]"
main:5: note: Possible overload variants:
main:5: note: def __get__(self, inst: None, own: Type[Base]) -> D
main:5: note: def __get__(self, inst: Base, own: Type[Base]) -> str
main:6: error: Revealed type is 'Any'
main:6: error: No overload variant of "__get__" of "D" matches argument types "A", "Type[A]"
main:6: note: Possible overload variants:
main:6: note: def __get__(self, inst: None, own: Type[Base]) -> D
main:6: note: def __get__(self, inst: Base, own: Type[Base]) -> str


[case testAccessingGenericNonDataDescriptor]
Expand Down Expand Up @@ -1325,6 +1331,10 @@ class D(Generic[T, V]):
[out]
main:5: error: Revealed type is 'Any'
main:5: error: No overload variant of "__get__" of "D" matches argument types "None", "Type[A]"
main:5: note: Possible overload variants:
main:5: note: def __get__(self, inst: None, own: None) -> D[A, int]
main:5: note: def __get__(self, inst: A, own: Type[A]) -> int


[case testAccessingNonDataDescriptorSubclass]
from typing import Any
Expand Down Expand Up @@ -2117,7 +2127,10 @@ class C:
c = C(1)
c.a # E: "C" has no attribute "a"
C('', '')
C('') # E: No overload variant of "C" matches argument type "str"
C('') # E: No overload variant of "C" matches argument type "str" \
# N: Possible overload variant: \
# N: def __new__(cls, foo: int) -> C \
# N: <1 more non-matching overload not shown>
[builtins fixtures/__new__.pyi]


Expand Down Expand Up @@ -2540,6 +2553,9 @@ u = new(User)
[builtins fixtures/classmethod.pyi]
[out]
tmp/foo.pyi:16: error: No overload variant of "User" matches argument type "str"
tmp/foo.pyi:16: note: Possible overload variant:
tmp/foo.pyi:16: note: def __init__(self, arg: int) -> U
tmp/foo.pyi:16: note: <1 more non-matching overload not shown>
tmp/foo.pyi:17: error: Too many arguments for "foo" of "User"

[case testTypeUsingTypeCInUpperBound]
Expand Down Expand Up @@ -2753,7 +2769,10 @@ def f(a: Type[User]) -> None: pass
@overload
def f(a: type) -> None: pass

f(3) # E: No overload variant of "f" matches argument type "int"
f(3) # E: No overload variant of "f" matches argument type "int" \
# N: Possible overload variants: \
# N: def f(a: Type[User]) -> None \
# N: def f(a: type) -> None
[builtins fixtures/classmethod.pyi]
[out]

Expand All @@ -2770,7 +2789,10 @@ def f(a: Type[User]) -> None: pass
def f(a: int) -> None: pass

f(User)
f(User()) # E: No overload variant of "f" matches argument type "User"
f(User()) # E: No overload variant of "f" matches argument type "User" \
# N: Possible overload variants: \
# N: def f(a: Type[User]) -> None \
# N: def f(a: int) -> None
[builtins fixtures/classmethod.pyi]
[out]

Expand All @@ -2791,10 +2813,16 @@ def f(a: Type[B]) -> None: pass
@overload
def f(a: int) -> None: pass

f(A) # E: No overload variant of "f" matches argument type "Type[A]"
f(A) # E: No overload variant of "f" matches argument type "Type[A]" \
# N: Possible overload variants: \
# N: def f(a: Type[B]) -> None \
# N: def f(a: int) -> None
f(B)
f(C)
f(AType) # E: No overload variant of "f" matches argument type "Type[A]"
f(AType) # E: No overload variant of "f" matches argument type "Type[A]" \
# N: Possible overload variants: \
# N: def f(a: Type[B]) -> None \
# N: def f(a: int) -> None
f(BType)
f(CType)
[builtins fixtures/classmethod.pyi]
Expand Down
5 changes: 4 additions & 1 deletion test-data/unit/check-dataclasses.test
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@ from dataclasses import dataclass, field
@dataclass
class Person:
name: str
age: int = field(init=None) # E: No overload variant of "field" matches argument type "None"
age: int = field(init=None) # E: No overload variant of "field" matches argument type "None" \
# N: Possible overload variant: \
# N: def field(*, init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ...) -> Any \
# N: <2 more non-matching overloads not shown>

[builtins fixtures/list.pyi]

Expand Down
5 changes: 4 additions & 1 deletion test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,10 @@ from typing import overload
a, b, c = None, None, None # type: (A, B, C)
a[b]
a[c]
a[1] # E: No overload variant of "__getitem__" of "A" matches argument type "int"
a[1] # E: No overload variant of "__getitem__" of "A" matches argument type "int" \
# N: Possible overload variants: \
# N: def __getitem__(self, B) -> int \
# N: def __getitem__(self, C) -> str

i, s = None, None # type: (int, str)
i = a[b]
Expand Down
5 changes: 4 additions & 1 deletion test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,10 @@ class A:
a = None # type: A
a.g()
a.g(B())
a.g(a) # E: No overload variant matches argument type "A"
a.g(a) # E: No overload variant matches argument type "A" \
# N: Possible overload variant: \
# N: def f(self, b: B) -> None \
# N: <1 more non-matching overload not shown>

[case testMethodAsDataAttributeInferredFromDynamicallyTypedMethod]

Expand Down
5 changes: 4 additions & 1 deletion test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -1470,7 +1470,10 @@ def f(blocks: Any): # E: Name 'Any' is not defined
[case testSpecialCaseEmptyListInitialization2]
def f(blocks: object):
to_process = [] # E: Need type annotation for 'to_process'
to_process = list(blocks) # E: No overload variant of "list" matches argument type "object"
to_process = list(blocks) # E: No overload variant of "list" matches argument type "object" \
# N: Possible overload variant: \
# N: def [T] __init__(self, x: Iterable[T]) -> List[T] \
# N: <1 more non-matching overload not shown>
[builtins fixtures/list.pyi]
[out]

Expand Down
Loading