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 2 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
9 changes: 7 additions & 2 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1198,12 +1198,17 @@ def check_overload_call(self,
elif 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)

return self.check_call(target, args, arg_kinds, context, arg_names,
arg_messages=arg_messages,
Expand Down
28 changes: 21 additions & 7 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,13 @@ 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.note('Possible overload variant(s):', context)
self.pretty_overload(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 @@ -1207,13 +1217,13 @@ def report_protocol_problems(self, subtype: Union[Instance, TupleType, TypedDict
self.note(self.pretty_callable(exp), context, offset=2 * OFFSET)
else:
assert isinstance(exp, Overloaded)
self.pretty_overload(exp, context, OFFSET, MAX_ITEMS)
self.pretty_overload(exp.items(), exp, context, OFFSET, MAX_ITEMS)
self.note('Got:', context, offset=OFFSET)
if isinstance(got, CallableType):
self.note(self.pretty_callable(got), context, offset=2 * OFFSET)
else:
assert isinstance(got, Overloaded)
self.pretty_overload(got, context, OFFSET, MAX_ITEMS)
self.pretty_overload(got.items(), got, context, OFFSET, MAX_ITEMS)
self.print_more(conflict_types, context, OFFSET, MAX_ITEMS)

# Report flag conflicts (i.e. settable vs read-only etc.)
Expand All @@ -1233,13 +1243,17 @@ 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, targets: List[CallableType], func: Overloaded, context: Context,
offset: int, max_items: int) -> None:
for item in tp.items()[:max_items]:
if len(targets) == 0:
Copy link
Member

Choose a reason for hiding this comment

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

if not targets: would be more idiomatic.

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),
shown = min(max_items, len(targets))
max_available = len(func.items())
if shown < max_available:
self.note('<{} more overload(s) not shown>'.format(max_available - shown),
Copy link
Member

Choose a reason for hiding this comment

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

Apart form the uglines around plurals, it seems there are two possible reasons why not all overloads are shown:

  • because they don't match the number of arguments (or the keywords or whatever)
  • because the sheer number exceeds max_items

I think it would be good to differentiate between these in the messages somehow.

context, offset=2 * offset)

def print_more(self, conflicts: Sequence[Any], context: Context,
Expand Down
14 changes: 12 additions & 2 deletions test-data/unit/check-abstract.test
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,12 @@ 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 variant(s): \
# N: @overload \
# N: def f(self, x: int) -> int \
# N: @overload \
# N: def f(self, x: str) -> str

[case testOverloadedAbstractMethodWithAlternativeDecoratorOrder]
from foo import *
Expand All @@ -552,7 +557,12 @@ 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 variant(s): \
# N: @overload \
# N: def f(self, x: int) -> int \
# N: @overload \
# N: def f(self, x: str) -> str

[case testOverloadedAbstractMethodVariantMissingDecorator1]
from foo import *
Expand Down
7 changes: 6 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,12 @@ 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 variant(s): \
# N: @overload \
# N: def method(self, y: str) -> str \
# N: @overload \
# N: def method(self, y: int) -> int

[case testNewNamedTupleMethodInheritance]
from typing import NamedTuple, TypeVar
Expand Down
54 changes: 49 additions & 5 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -1225,8 +1225,18 @@ 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 variant(s):
main:5: note: @overload
main:5: note: def __get__(self, inst: None, own: Type[Base]) -> D
main:5: note: @overload
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 variant(s):
main:6: note: @overload
main:6: note: def __get__(self, inst: None, own: Type[Base]) -> D
main:6: note: @overload
main:6: note: def __get__(self, inst: Base, own: Type[Base]) -> str


[case testAccessingGenericNonDataDescriptor]
Expand Down Expand Up @@ -1325,6 +1335,12 @@ 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 variant(s):
main:5: note: @overload
main:5: note: def __get__(self, inst: None, own: None) -> D[A, int]
main:5: note: @overload
main:5: note: def __get__(self, inst: A, own: Type[A]) -> int


[case testAccessingNonDataDescriptorSubclass]
from typing import Any
Expand Down Expand Up @@ -2117,7 +2133,11 @@ 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(s): \
# N: @overload \
# N: def __new__(cls, foo: int) -> C \
# N: <1 more overload(s) not shown>
[builtins fixtures/__new__.pyi]


Expand Down Expand Up @@ -2540,6 +2560,10 @@ 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(s):
tmp/foo.pyi:16: note: @overload
tmp/foo.pyi:16: note: def __init__(self, arg: int) -> U
tmp/foo.pyi:16: note: <1 more overload(s) not shown>
tmp/foo.pyi:17: error: Too many arguments for "foo" of "User"

[case testTypeUsingTypeCInUpperBound]
Expand Down Expand Up @@ -2753,7 +2777,12 @@ 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 variant(s): \
# N: @overload \
# N: def f(a: Type[User]) -> None \
# N: @overload \
# N: def f(a: type) -> None
[builtins fixtures/classmethod.pyi]
[out]

Expand All @@ -2770,7 +2799,12 @@ 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 variant(s): \
# N: @overload \
# N: def f(a: Type[User]) -> None \
# N: @overload \
# N: def f(a: int) -> None
[builtins fixtures/classmethod.pyi]
[out]

Expand All @@ -2791,10 +2825,20 @@ 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 variant(s): \
# N: @overload \
# N: def f(a: Type[B]) -> None \
# N: @overload \
# 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 variant(s): \
# N: @overload \
# N: def f(a: Type[B]) -> None \
# N: @overload \
# N: def f(a: int) -> None
f(BType)
f(CType)
[builtins fixtures/classmethod.pyi]
Expand Down
6 changes: 5 additions & 1 deletion test-data/unit/check-dataclasses.test
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ 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(s): \
# N: @overload \
# N: def field(*, init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ...) -> Any \
# N: <2 more overload(s) not shown>

[builtins fixtures/list.pyi]

Expand Down
7 changes: 6 additions & 1 deletion test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,12 @@ 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 variant(s): \
# N: @overload \
# N: def __getitem__(self, B) -> int \
# N: @overload \
# N: def __getitem__(self, C) -> str

i, s = None, None # type: (int, str)
i = a[b]
Expand Down
6 changes: 5 additions & 1 deletion test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,11 @@ 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(s): \
# N: @overload \
# N: def f(self, b: B) -> None \
# N: <1 more overload(s) not shown>

[case testMethodAsDataAttributeInferredFromDynamicallyTypedMethod]

Expand Down
6 changes: 5 additions & 1 deletion test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -1470,7 +1470,11 @@ 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(s): \
# N: @overload \
# N: def [T] __init__(self, x: Iterable[T]) -> List[T] \
# N: <1 more overload(s) not shown>
[builtins fixtures/list.pyi]
[out]

Expand Down
Loading