From 3ebeee4877676b39edef95fb97c24e3e15b118e4 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Thu, 28 Jun 2018 22:06:12 -0700 Subject: [PATCH 1/5] List possible overload variants when none match Resolves https://github.com/python/mypy/issues/672 and supercedes https://github.com/python/mypy/pull/5054. Currently, if a user calls an overloaded function and mypy is unable to find a match, mypy will display display a warning like 'No overload variant of "func" matches argument types" with no additional context. This pull request will list several matching variants in addition to that message. If possible, mypy will attempt to show only overloads that have a compatible number of arguments. The number of overloads shown is always capped at a maximum of 2. This pull request does *not* attempt to report a custom error when... 1. Union-math fails. Rationale: the pending PR will change how unions are handled dramatically enough to the point where any error handling here would be pointless. 2. Mypy is able to infer what signature the user most likely meant by looking at the erased types. Rationale: I attempted to implement this feature but was unable to make it consistently work without adding several ugly hacks around how mypy records errors so decided to defer implementing this feature. --- mypy/checkexpr.py | 9 +- mypy/messages.py | 28 +++-- test-data/unit/check-abstract.test | 14 ++- test-data/unit/check-class-namedtuple.test | 7 +- test-data/unit/check-classes.test | 44 +++++++- test-data/unit/check-expressions.test | 7 +- test-data/unit/check-functions.test | 6 +- test-data/unit/check-overloading.test | 122 ++++++++++++++++++--- test-data/unit/check-protocols.test | 7 +- test-data/unit/check-serialize.test | 10 ++ test-data/unit/check-typeddict.test | 7 +- test-data/unit/fine-grained.test | 24 ++++ 12 files changed, 246 insertions(+), 39 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index bf36f83985fe..6e5f17932aeb 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -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, diff --git a/mypy/messages.py b/mypy/messages.py index a9276bde6ec4..c7cbe1f5ef1d 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -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: @@ -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: @@ -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.) @@ -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: + targets = func.items() + for item in targets[:max_items]: self.note('@overload', context, offset=2 * offset) 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), context, offset=2 * offset) def print_more(self, conflicts: Sequence[Any], context: Context, diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index 9aa0b87adff8..aaffcabe0b9f 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -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 * @@ -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 * diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test index 4e134910e772..cfbce9d7a99a 100644 --- a/test-data/unit/check-class-namedtuple.test +++ b/test-data/unit/check-class-namedtuple.test @@ -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 diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 0f955a5e48a6..abf8bb12c53f 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1325,6 +1325,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 @@ -2117,7 +2123,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] @@ -2540,6 +2550,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] @@ -2753,7 +2767,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] @@ -2770,7 +2789,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] @@ -2791,10 +2815,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] diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 5772238e3fa3..c9eb56a4fa78 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -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] diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 3b900ee5f5fc..08a17f4e04a4 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -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] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 0d3a6e444300..8e71f1736132 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -386,7 +386,12 @@ class B: pass from foo import * [file foo.pyi] from typing import overload -f(C()) # E: No overload variant of "f" matches argument type "C" +f(C()) # E: No overload variant of "f" matches argument type "C" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(x: A) -> None \ + # N: @overload \ + # N: def f(x: B) -> None f(A()) f(B()) @@ -420,7 +425,12 @@ class B: pass from foo import * [file foo.pyi] from typing import overload -A().f(C()) # E: No overload variant of "f" of "A" matches argument type "C" +A().f(C()) # E: No overload variant of "f" of "A" matches argument type "C" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(self, x: A) -> None \ + # N: @overload \ + # N: def f(self, x: B) -> None A().f(A()) A().f(B()) @@ -457,11 +467,23 @@ from typing import overload a, b = None, None # type: (A, B) a = f(a) b = f(a) # E: Incompatible types in assignment (expression has type "A", variable has type "B") -f(b) # E: No overload variant of "f" matches argument type "B" +f(b) # E: No overload variant of "f" matches argument type "B" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(x: A) -> A \ + # N: <1 more overload(s) not shown> b = f(b, a) a = f(b, a) # E: Incompatible types in assignment (expression has type "B", variable has type "A") -f(a, a) # E: No overload variant of "f" matches argument types "A", "A" -f(b, b) # E: No overload variant of "f" matches argument types "B", "B" +f(a, a) # E: No overload variant of "f" matches argument types "A", "A" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(x: B, y: A) -> B \ + # N: <1 more overload(s) not shown> +f(b, b) # E: No overload variant of "f" matches argument types "B", "B" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(x: B, y: A) -> B \ + # N: <1 more overload(s) not shown> @overload def f(x: 'A') -> 'A': pass @@ -496,7 +518,12 @@ from typing import overload a, b = None, None # type: (A, B) a = A(a) a = A(b) -a = A(object()) # E: No overload variant of "A" matches argument type "object" +a = A(object()) # E: No overload variant of "A" matches argument type "object" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def __init__(self, a: A) -> A \ + # N: @overload \ + # N: def __init__(self, b: B) -> A class A: @overload @@ -617,7 +644,12 @@ f(A(), A, A) f(B()) f(B(), B) f(B(), B, B) -f(object()) # E: No overload variant of "f" matches argument type "object" +f(object()) # E: No overload variant of "f" matches argument type "object" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(x: A, *more: Any) -> A \ + # N: @overload \ + # N: def f(x: B, *more: Any) -> A class A: pass class B: pass [builtins fixtures/list.pyi] @@ -632,8 +664,18 @@ def f(x: 'A', *more: 'B') -> 'A': pass def f(x: 'B', *more: 'A') -> 'A': pass f(A(), B()) f(A(), B(), B()) -f(A(), A(), B()) # E: No overload variant of "f" matches argument types "A", "A", "B" -f(A(), B(), A()) # E: No overload variant of "f" matches argument types "A", "B", "A" +f(A(), A(), B()) # E: No overload variant of "f" matches argument types "A", "A", "B" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(x: A, *more: B) -> A \ + # N: @overload \ + # N: def f(x: B, *more: A) -> A +f(A(), B(), A()) # E: No overload variant of "f" matches argument types "A", "B", "A" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(x: A, *more: B) -> A \ + # N: @overload \ + # N: def f(x: B, *more: A) -> A class A: pass class B: pass [builtins fixtures/list.pyi] @@ -798,7 +840,12 @@ A() < B() B() < A() B() < B() A() < object() # E: Unsupported operand types for < ("A" and "object") -B() < object() # E: No overload variant of "__lt__" of "B" matches argument type "object" +B() < object() # E: No overload variant of "__lt__" of "B" matches argument type "object" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def __lt__(self, B) -> int \ + # N: @overload \ + # N: def __lt__(self, A) -> int [case testOverloadedForwardMethodAndCallingReverseMethod] from foo import * @@ -814,7 +861,12 @@ class B: A() + A() A() + 1 A() + B() -A() + '' # E: No overload variant of "__add__" of "A" matches argument type "str" +A() + '' # E: No overload variant of "__add__" of "A" matches argument type "str" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def __add__(self, A) -> int \ + # N: @overload \ + # N: def __add__(self, int) -> int [case testOverrideOverloadedMethodWithMoreGeneralArgumentTypes] from foo import * @@ -899,7 +951,12 @@ def f(x: str) -> None: pass f(1.1) f('') f(1) -f(()) # E: No overload variant of "f" matches argument type "Tuple[]" +f(()) # E: No overload variant of "f" matches argument type "Tuple[]" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(x: float) -> None \ + # N: @overload \ + # N: def f(x: str) -> None [builtins fixtures/primitives.pyi] [out] @@ -965,10 +1022,18 @@ from typing import overload def f(x: int, y: str) -> int: pass @overload def f(*x: str) -> str: pass -f(*(1,))() # E: No overload variant of "f" matches argument type "Tuple[int]" +f(*(1,))() # E: No overload variant of "f" matches argument type "Tuple[int]" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(*x: str) -> str \ + # N: <1 more overload(s) not shown> f(*('',))() # E: "str" not callable f(*(1, ''))() # E: "int" not callable -f(*(1, '', 1))() # E: No overload variant of "f" matches argument type "Tuple[int, str, int]" +f(*(1, '', 1))() # E: No overload variant of "f" matches argument type "Tuple[int, str, int]" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(*x: str) -> str \ + # N: <1 more overload(s) not shown> [case testPreferExactSignatureMatchInOverload] from foo import * @@ -1015,7 +1080,12 @@ class mystr(str): pass f('x')() # E: "str" not callable f(1)() # E: "bool" not callable -f(1.1) # E: No overload variant of "f" matches argument type "float" +f(1.1) # E: No overload variant of "f" matches argument type "float" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def [T <: str] f(x: T) -> T \ + # N: @overload \ + # N: def f(x: int) -> bool f(mystr())() # E: "mystr" not callable [builtins fixtures/primitives.pyi] @@ -1034,7 +1104,12 @@ U = TypeVar('U', bound=mystr) V = TypeVar('V') def g(x: U, y: V) -> None: f(x)() # E: "mystr" not callable - f(y) # E: No overload variant of "f" matches argument type "V" + f(y) # E: No overload variant of "f" matches argument type "V" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def [T <: str] f(x: T) -> T \ + # N: @overload \ + # N: def [T <: str] f(x: List[T]) -> None a = f([x]) # E: "f" does not return a value f([y]) # E: Value of type variable "T" of "f" cannot be "V" f([x, y]) # E: Value of type variable "T" of "f" cannot be "object" @@ -1301,7 +1376,12 @@ from typing import overload, Callable def f(a: Callable[[], int]) -> None: pass @overload def f(a: str) -> None: pass -f(0) # E: No overload variant of "f" matches argument type "int" +f(0) # E: No overload variant of "f" matches argument type "int" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(a: Callable[[], int]) -> None \ + # N: @overload \ + # N: def f(a: str) -> None [case testCustomRedefinitionDecorator] from typing import Any, Callable, Type @@ -2171,7 +2251,13 @@ x: List[int] reveal_type(foo(*x)) # E: Revealed type is '__main__.C' y: List[str] -foo(*y) # E: No overload variant of "foo" matches argument type "List[str]" +foo(*y) # E: No overload variant of "foo" matches argument type "List[str]" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def foo(x: int, y: int, z: int, *args: int) -> C \ + # N: @overload \ + # N: def foo(x: int) -> A \ + # N: <1 more overload(s) not shown> [builtins fixtures/list.pyi] [case testOverloadMultipleVarargDefinition] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 14e1129cc78a..bb0f1fdea23a 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -1303,7 +1303,12 @@ reveal_type(f(C2())) # E: Revealed type is 'builtins.str' class D(C1, C2): pass # Compatible with both P1 and P2 # TODO: Should this return a union instead? reveal_type(f(D())) # E: Revealed type is 'builtins.int' -f(C()) # E: No overload variant of "f" matches argument type "C" +f(C()) # E: No overload variant of "f" matches argument type "C" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(x: P1) -> int \ + # N: @overload \ + # N: def f(x: P2) -> str [builtins fixtures/isinstance.pyi] -- Unions of protocol types diff --git a/test-data/unit/check-serialize.test b/test-data/unit/check-serialize.test index 19a066528620..83f72d59a281 100644 --- a/test-data/unit/check-serialize.test +++ b/test-data/unit/check-serialize.test @@ -294,7 +294,17 @@ class A: def __init__(self, x: str) -> None: pass [out2] tmp/a.py:2: error: No overload variant of "A" matches argument type "object" +tmp/a.py:2: note: Possible overload variant(s): +tmp/a.py:2: note: @overload +tmp/a.py:2: note: def A(self, x: int) -> A +tmp/a.py:2: note: @overload +tmp/a.py:2: note: def A(self, x: str) -> A tmp/a.py:7: error: No overload variant of "__init__" of "A" matches argument type "object" +tmp/a.py:7: note: Possible overload variant(s): +tmp/a.py:7: note: @overload +tmp/a.py:7: note: def __init__(self, x: int) -> None +tmp/a.py:7: note: @overload +tmp/a.py:7: note: def __init__(self, x: str) -> None [case testSerialize__new__] import a diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 2619e9c8f8ab..17dca3e126eb 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -1205,7 +1205,12 @@ def f(x: int) -> None: ... def f(x): pass a: A -f(a) # E: No overload variant of "f" matches argument type "A" +f(a) # E: No overload variant of "f" matches argument type "A" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(x: str) -> None \ + # N: @overload \ + # N: def f(x: int) -> None [builtins fixtures/dict.pyi] [typing fixtures/typing-full.pyi] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 9d0205bd941e..c61d844a9859 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -2113,6 +2113,11 @@ main:3: error: Revealed type is 'builtins.int' == main:3: error: Revealed type is 'Any' main:3: error: No overload variant of "foo" of "Wrapper" matches argument type "int" +main:3: note: Possible overload variant(s): +main:3: note: @overload +main:3: note: def foo(cls: Wrapper, x: int) -> int +main:3: note: @overload +main:3: note: def foo(cls: Wrapper, x: str) -> str [case testRefreshGenericClass] from typing import TypeVar, Generic @@ -6334,6 +6339,11 @@ def f(x): [out] == main:3: error: No overload variant of "f" matches argument type "str" +main:3: note: Possible overload variant(s): +main:3: note: @overload +main:3: note: def f(x: int) -> None +main:3: note: @overload +main:3: note: def f(x: C) -> int [case testOverloadsDeleted] import mod @@ -6443,6 +6453,11 @@ T = TypeVar('T', bound=str) [out] == a.py:2: error: No overload variant of "f" matches argument type "int" +a.py:2: note: Possible overload variant(s): +a.py:2: note: @overload +a.py:2: note: def f(x: C) -> None +a.py:2: note: @overload +a.py:2: note: def [c.T <: str] f(x: c.T) -> c.T [case testOverloadsGenericToNonGeneric] import a @@ -6468,6 +6483,11 @@ class T: pass [out] == a.py:2: error: No overload variant of "f" matches argument type "int" +a.py:2: note: Possible overload variant(s): +a.py:2: note: @overload +a.py:2: note: def f(x: C) -> None +a.py:2: note: @overload +a.py:2: note: def f(x: T) -> T [case testOverloadsToNonOverloaded] import a @@ -6623,6 +6643,10 @@ class C: [out] == a.py:2: error: No overload variant of "B" matches argument type "int" +a.py:2: note: Possible overload variant(s): +a.py:2: note: @overload +a.py:2: note: def __init__(self, x: str) -> B +a.py:2: note: <1 more overload(s) not shown> [case testOverloadedToNormalMethodMetaclass] import a From 67d8e7d0587879cf9728127e319d26fcbc5eebcd Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Thu, 28 Jun 2018 22:57:20 -0700 Subject: [PATCH 2/5] Fix missed test cases --- test-data/unit/check-classes.test | 10 ++++++++++ test-data/unit/check-dataclasses.test | 6 +++++- test-data/unit/check-inference.test | 6 +++++- test-data/unit/check-overloading.test | 9 +++++++-- test-data/unit/check-typeddict.test | 14 ++++++++++++-- test-data/unit/python2eval.test | 5 +++++ test-data/unit/pythoneval.test | 5 +++++ 7 files changed, 49 insertions(+), 6 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index abf8bb12c53f..a4d3fd4e6a61 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -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] diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index e79202aafcd1..3e9d80b5b675 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -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] diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 3ace7c981612..faddaecc20c0 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -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] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 8e71f1736132..77e0ad795ac4 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -1142,7 +1142,7 @@ def i(x: List[str]) -> int: pass # E: Overloaded function signatures 1 and 2 ov def i(x: List[T]) -> None: pass [builtins fixtures/list.pyi] -[case testOverlapWithTypeVarsWithValues] +[case testOverloadOverlapWithTypeVarsWithValues] from foo import * [file foo.pyi] from typing import overload, TypeVar @@ -1156,7 +1156,12 @@ def f(x: AnyStr) -> str: pass f(1)() # E: "int" not callable f('1')() # E: "str" not callable f(b'1')() # E: "str" not callable -f(1.0) # E: No overload variant of "f" matches argument type "float" +f(1.0) # E: No overload variant of "f" matches argument type "float" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def f(x: int) -> int \ + # N: @overload \ + # N: def [AnyStr in (bytes, str)] f(x: AnyStr) -> str @overload def g(x: AnyStr, *a: AnyStr) -> None: pass diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 17dca3e126eb..4f2dd6651437 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -906,8 +906,18 @@ reveal_type(d.get('x', a)) # E: Revealed type is 'Union[builtins.list[builtins.i from mypy_extensions import TypedDict D = TypedDict('D', {'x': int, 'y': str}) d: D -d.get() # E: All overload variants of "get" of "Mapping" require at least one argument -d.get('x', 1, 2) # E: No overload variant of "get" of "Mapping" matches argument types "str", "int", "int" +d.get() # E: All overload variants of "get" of "Mapping" require at least one argument \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def get(self, k: str) -> object \ + # N: @overload \ + # N: def [V] get(self, k: str, default: object) -> object +d.get('x', 1, 2) # E: No overload variant of "get" of "Mapping" matches argument types "str", "int", "int" \ + # N: Possible overload variant(s): \ + # N: @overload \ + # N: def get(self, k: str) -> object \ + # N: @overload \ + # N: def [V] get(self, k: str, default: Union[int, V]) -> object x = d.get('z') # E: TypedDict "D" has no key 'z' reveal_type(x) # E: Revealed type is 'Any' s = '' diff --git a/test-data/unit/python2eval.test b/test-data/unit/python2eval.test index d92fab57e4be..d1c9fb381d3d 100644 --- a/test-data/unit/python2eval.test +++ b/test-data/unit/python2eval.test @@ -320,6 +320,11 @@ def f(x): # type: (unicode) -> int pass [out] _program.py:2: error: No overload variant of "f" matches argument type "int" +_program.py:2: note: Possible overload variant(s): +_program.py:2: note: @overload +_program.py:2: note: def f(x: bytearray) -> int +_program.py:2: note: @overload +_program.py:2: note: def f(x: unicode) -> int [case testByteArrayStrCompatibility_python2] def f(x): # type: (str) -> None diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 0bb9da3525fa..df3536e92470 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1045,6 +1045,11 @@ _testTypedDictGet.py:7: error: Revealed type is 'builtins.int' _testTypedDictGet.py:8: error: Revealed type is 'builtins.str' _testTypedDictGet.py:9: error: TypedDict "D" has no key 'z' _testTypedDictGet.py:10: error: All overload variants of "get" of "Mapping" require at least one argument +_testTypedDictGet.py:10: note: Possible overload variant(s): +_testTypedDictGet.py:10: note: @overload +_testTypedDictGet.py:10: note: def get(self, k: str) -> object +_testTypedDictGet.py:10: note: @overload +_testTypedDictGet.py:10: note: def [_T] get(self, k: str, default: object) -> object _testTypedDictGet.py:12: error: Revealed type is 'builtins.object*' [case testTypedDictMappingMethods] From c363f626178e475b6b2b1d07115fa7028a0c2afb Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Sun, 1 Jul 2018 11:23:50 -0700 Subject: [PATCH 3/5] Address code review --- mypy/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/messages.py b/mypy/messages.py index c7cbe1f5ef1d..05b258500147 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1245,7 +1245,7 @@ def report_protocol_problems(self, subtype: Union[Instance, TupleType, TypedDict def pretty_overload(self, targets: List[CallableType], func: Overloaded, context: Context, offset: int, max_items: int) -> None: - if len(targets) == 0: + if not targets: targets = func.items() for item in targets[:max_items]: self.note('@overload', context, offset=2 * offset) From 83f02753302cc5e9e4ca9fdd3591ff853160c285 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Wed, 4 Jul 2018 01:10:20 -0700 Subject: [PATCH 4/5] Respond to code review: enhance error messages This commit responds to the latest code review. It... 1. Handles plurals. 2. Removes the '@overload' text to help save space. 3. Modifies the 'some overloads not shown' message so it instead displays one of two variants: if we aren't showing all matching variants, we display: This happens if there are more then 2 matching overloads. If we do show all matching variants but hide non-matching overloads, we display: 4. Moves all of the new overload display functionality back to a new method named `pretty_overload_matches` and reverts `pretty_overload` back to the original logic. (The new logic was growing to be different enough from the previous logic that a split seemed like the cleanest approach.) --- mypy/messages.py | 59 ++++++++++----- test-data/unit/check-abstract.test | 8 +-- test-data/unit/check-class-namedtuple.test | 4 +- test-data/unit/check-classes.test | 38 +++------- test-data/unit/check-dataclasses.test | 5 +- test-data/unit/check-expressions.test | 4 +- test-data/unit/check-functions.test | 5 +- test-data/unit/check-inference.test | 5 +- test-data/unit/check-overloading.test | 83 +++++++--------------- test-data/unit/check-protocols.test | 6 +- test-data/unit/check-serialize.test | 8 +-- test-data/unit/check-typeddict.test | 12 +--- test-data/unit/fine-grained.test | 21 ++---- test-data/unit/python2eval.test | 4 +- test-data/unit/pythoneval.test | 4 +- 15 files changed, 104 insertions(+), 162 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 05b258500147..9cb5a5fe14e9 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -771,12 +771,7 @@ def no_variant_matches_arguments(self, 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) + 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: @@ -1217,13 +1212,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.items(), exp, context, OFFSET, MAX_ITEMS) + self.pretty_overload(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.items(), got, context, OFFSET, MAX_ITEMS) + self.pretty_overload(got, context, OFFSET, MAX_ITEMS) self.print_more(conflict_types, context, OFFSET, MAX_ITEMS) # Report flag conflicts (i.e. settable vs read-only etc.) @@ -1243,18 +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, targets: List[CallableType], func: Overloaded, context: Context, + def pretty_overload(self, func: Overloaded, context: Context, offset: int, max_items: int) -> None: - if not targets: - targets = func.items() + targets = func.items() for item in targets[:max_items]: self.note('@overload', context, offset=2 * offset) self.note(self.pretty_callable(item), 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) + + 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()) - if shown < max_available: - self.note('<{} more overload(s) not shown>'.format(max_available - shown), - context, offset=2 * offset) + + 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( + 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: @@ -1419,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 '' diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index aaffcabe0b9f..19641b288c0a 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -526,10 +526,8 @@ a = B() # type: A a.f(1) a.f('') a.f(B()) # E: No overload variant of "f" of "A" matches argument type "B" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(self, x: int) -> int \ - # N: @overload \ # N: def f(self, x: str) -> str [case testOverloadedAbstractMethodWithAlternativeDecoratorOrder] @@ -558,10 +556,8 @@ a = B() # type: A a.f(1) a.f('') a.f(B()) # E: No overload variant of "f" of "A" matches argument type "B" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(self, x: int) -> int \ - # N: @overload \ # N: def f(self, x: str) -> str [case testOverloadedAbstractMethodVariantMissingDecorator1] diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test index cfbce9d7a99a..cf5bb46fc647 100644 --- a/test-data/unit/check-class-namedtuple.test +++ b/test-data/unit/check-class-namedtuple.test @@ -524,10 +524,8 @@ 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]" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def method(self, y: str) -> str \ - # N: @overload \ # N: def method(self, y: int) -> int [case testNewNamedTupleMethodInheritance] diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index aa56ce1f5c75..629c00ff817d 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1225,17 +1225,13 @@ 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: Possible overload variants: 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: Possible overload variants: 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 @@ -1335,10 +1331,8 @@ 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: Possible overload variants: 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 @@ -2134,10 +2128,9 @@ c = C(1) c.a # E: "C" has no attribute "a" C('', '') C('') # E: No overload variant of "C" matches argument type "str" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variant: \ # N: def __new__(cls, foo: int) -> C \ - # N: <1 more overload(s) not shown> + # N: <1 more non-matching overload not shown> [builtins fixtures/__new__.pyi] @@ -2560,10 +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(s): -tmp/foo.pyi:16: note: @overload +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 overload(s) not shown> +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] @@ -2778,10 +2770,8 @@ def f(a: Type[User]) -> None: pass def f(a: type) -> None: pass f(3) # E: No overload variant of "f" matches argument type "int" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(a: Type[User]) -> None \ - # N: @overload \ # N: def f(a: type) -> None [builtins fixtures/classmethod.pyi] [out] @@ -2800,10 +2790,8 @@ def f(a: int) -> None: pass f(User) f(User()) # E: No overload variant of "f" matches argument type "User" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(a: Type[User]) -> None \ - # N: @overload \ # N: def f(a: int) -> None [builtins fixtures/classmethod.pyi] [out] @@ -2826,18 +2814,14 @@ def f(a: Type[B]) -> None: pass def f(a: int) -> None: pass f(A) # E: No overload variant of "f" matches argument type "Type[A]" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # 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]" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(a: Type[B]) -> None \ - # N: @overload \ # N: def f(a: int) -> None f(BType) f(CType) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 3e9d80b5b675..7723c7c81a05 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -121,10 +121,9 @@ from dataclasses import dataclass, field class Person: name: str age: int = field(init=None) # E: No overload variant of "field" matches argument type "None" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # 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 overload(s) not shown> + # N: <2 more non-matching overloads not shown> [builtins fixtures/list.pyi] diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index c9eb56a4fa78..9d0b98d02d61 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -867,10 +867,8 @@ 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" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def __getitem__(self, B) -> int \ - # N: @overload \ # N: def __getitem__(self, C) -> str i, s = None, None # type: (int, str) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 08a17f4e04a4..64936e30623b 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -535,10 +535,9 @@ a = None # type: A a.g() a.g(B()) a.g(a) # E: No overload variant matches argument type "A" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variant: \ # N: def f(self, b: B) -> None \ - # N: <1 more overload(s) not shown> + # N: <1 more non-matching overload not shown> [case testMethodAsDataAttributeInferredFromDynamicallyTypedMethod] diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index faddaecc20c0..67ebfa917f97 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -1471,10 +1471,9 @@ def f(blocks: Any): # E: Name 'Any' is not defined 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" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variant: \ # N: def [T] __init__(self, x: Iterable[T]) -> List[T] \ - # N: <1 more overload(s) not shown> + # N: <1 more non-matching overload not shown> [builtins fixtures/list.pyi] [out] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 9572428675a2..2dcd5e85c573 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -387,10 +387,8 @@ from foo import * [file foo.pyi] from typing import overload f(C()) # E: No overload variant of "f" matches argument type "C" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(x: A) -> None \ - # N: @overload \ # N: def f(x: B) -> None f(A()) f(B()) @@ -426,10 +424,8 @@ from foo import * [file foo.pyi] from typing import overload A().f(C()) # E: No overload variant of "f" of "A" matches argument type "C" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(self, x: A) -> None \ - # N: @overload \ # N: def f(self, x: B) -> None A().f(A()) A().f(B()) @@ -468,22 +464,19 @@ a, b = None, None # type: (A, B) a = f(a) b = f(a) # E: Incompatible types in assignment (expression has type "A", variable has type "B") f(b) # E: No overload variant of "f" matches argument type "B" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variant: \ # N: def f(x: A) -> A \ - # N: <1 more overload(s) not shown> + # N: <1 more non-matching overload not shown> b = f(b, a) a = f(b, a) # E: Incompatible types in assignment (expression has type "B", variable has type "A") f(a, a) # E: No overload variant of "f" matches argument types "A", "A" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variant: \ # N: def f(x: B, y: A) -> B \ - # N: <1 more overload(s) not shown> + # N: <1 more non-matching overload not shown> f(b, b) # E: No overload variant of "f" matches argument types "B", "B" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variant: \ # N: def f(x: B, y: A) -> B \ - # N: <1 more overload(s) not shown> + # N: <1 more non-matching overload not shown> @overload def f(x: 'A') -> 'A': pass @@ -519,10 +512,8 @@ a, b = None, None # type: (A, B) a = A(a) a = A(b) a = A(object()) # E: No overload variant of "A" matches argument type "object" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def __init__(self, a: A) -> A \ - # N: @overload \ # N: def __init__(self, b: B) -> A class A: @@ -645,10 +636,8 @@ f(B()) f(B(), B) f(B(), B, B) f(object()) # E: No overload variant of "f" matches argument type "object" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(x: A, *more: Any) -> A \ - # N: @overload \ # N: def f(x: B, *more: Any) -> A class A: pass class B: pass @@ -665,16 +654,12 @@ def f(x: 'B', *more: 'A') -> 'A': pass f(A(), B()) f(A(), B(), B()) f(A(), A(), B()) # E: No overload variant of "f" matches argument types "A", "A", "B" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(x: A, *more: B) -> A \ - # N: @overload \ # N: def f(x: B, *more: A) -> A f(A(), B(), A()) # E: No overload variant of "f" matches argument types "A", "B", "A" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(x: A, *more: B) -> A \ - # N: @overload \ # N: def f(x: B, *more: A) -> A class A: pass class B: pass @@ -841,10 +826,8 @@ B() < A() B() < B() A() < object() # E: Unsupported operand types for < ("A" and "object") B() < object() # E: No overload variant of "__lt__" of "B" matches argument type "object" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def __lt__(self, B) -> int \ - # N: @overload \ # N: def __lt__(self, A) -> int [case testOverloadedForwardMethodAndCallingReverseMethod] @@ -862,10 +845,8 @@ A() + A() A() + 1 A() + B() A() + '' # E: No overload variant of "__add__" of "A" matches argument type "str" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def __add__(self, A) -> int \ - # N: @overload \ # N: def __add__(self, int) -> int [case testOverrideOverloadedMethodWithMoreGeneralArgumentTypes] @@ -952,10 +933,8 @@ f(1.1) f('') f(1) f(()) # E: No overload variant of "f" matches argument type "Tuple[]" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(x: float) -> None \ - # N: @overload \ # N: def f(x: str) -> None [builtins fixtures/primitives.pyi] [out] @@ -1023,17 +1002,15 @@ def f(x: int, y: str) -> int: pass @overload def f(*x: str) -> str: pass f(*(1,))() # E: No overload variant of "f" matches argument type "Tuple[int]" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variant: \ # N: def f(*x: str) -> str \ - # N: <1 more overload(s) not shown> + # N: <1 more non-matching overload not shown> f(*('',))() # E: "str" not callable f(*(1, ''))() # E: "int" not callable f(*(1, '', 1))() # E: No overload variant of "f" matches argument type "Tuple[int, str, int]" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variant: \ # N: def f(*x: str) -> str \ - # N: <1 more overload(s) not shown> + # N: <1 more non-matching overload not shown> [case testPreferExactSignatureMatchInOverload] from foo import * @@ -1081,10 +1058,8 @@ class mystr(str): pass f('x')() # E: "str" not callable f(1)() # E: "bool" not callable f(1.1) # E: No overload variant of "f" matches argument type "float" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def [T <: str] f(x: T) -> T \ - # N: @overload \ # N: def f(x: int) -> bool f(mystr())() # E: "mystr" not callable [builtins fixtures/primitives.pyi] @@ -1105,10 +1080,8 @@ V = TypeVar('V') def g(x: U, y: V) -> None: f(x)() # E: "mystr" not callable f(y) # E: No overload variant of "f" matches argument type "V" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def [T <: str] f(x: T) -> T \ - # N: @overload \ # N: def [T <: str] f(x: List[T]) -> None a = f([x]) # E: "f" does not return a value f([y]) # E: Value of type variable "T" of "f" cannot be "V" @@ -1157,10 +1130,8 @@ f(1)() # E: "int" not callable f('1')() # E: "str" not callable f(b'1')() # E: "str" not callable f(1.0) # E: No overload variant of "f" matches argument type "float" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(x: int) -> int \ - # N: @overload \ # N: def [AnyStr in (bytes, str)] f(x: AnyStr) -> str @overload @@ -1382,10 +1353,8 @@ def f(a: Callable[[], int]) -> None: pass @overload def f(a: str) -> None: pass f(0) # E: No overload variant of "f" matches argument type "int" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(a: Callable[[], int]) -> None \ - # N: @overload \ # N: def f(a: str) -> None [case testCustomRedefinitionDecorator] @@ -2257,12 +2226,10 @@ reveal_type(foo(*x)) # E: Revealed type is '__main__.C' y: List[str] foo(*y) # E: No overload variant of "foo" matches argument type "List[str]" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def foo(x: int, y: int, z: int, *args: int) -> C \ - # N: @overload \ # N: def foo(x: int) -> A \ - # N: <1 more overload(s) not shown> + # N: <1 more matching overload not shown, out of 3 total overloads> [builtins fixtures/list.pyi] [case testOverloadMultipleVarargDefinition] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index bb0f1fdea23a..82e7582c5988 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -1304,10 +1304,8 @@ class D(C1, C2): pass # Compatible with both P1 and P2 # TODO: Should this return a union instead? reveal_type(f(D())) # E: Revealed type is 'builtins.int' f(C()) # E: No overload variant of "f" matches argument type "C" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(x: P1) -> int \ - # N: @overload \ # N: def f(x: P2) -> str [builtins fixtures/isinstance.pyi] @@ -2010,7 +2008,7 @@ main:18: note: @overload main:18: note: def f(self, x: int) -> int main:18: note: @overload main:18: note: def f(self, x: str) -> str -main:18: note: <2 more overload(s) not shown> +main:18: note: <2 more overloads not shown> main:18: note: Got: main:18: note: def f(self) -> None diff --git a/test-data/unit/check-serialize.test b/test-data/unit/check-serialize.test index 83f72d59a281..028dc6847d09 100644 --- a/test-data/unit/check-serialize.test +++ b/test-data/unit/check-serialize.test @@ -294,16 +294,12 @@ class A: def __init__(self, x: str) -> None: pass [out2] tmp/a.py:2: error: No overload variant of "A" matches argument type "object" -tmp/a.py:2: note: Possible overload variant(s): -tmp/a.py:2: note: @overload +tmp/a.py:2: note: Possible overload variants: tmp/a.py:2: note: def A(self, x: int) -> A -tmp/a.py:2: note: @overload tmp/a.py:2: note: def A(self, x: str) -> A tmp/a.py:7: error: No overload variant of "__init__" of "A" matches argument type "object" -tmp/a.py:7: note: Possible overload variant(s): -tmp/a.py:7: note: @overload +tmp/a.py:7: note: Possible overload variants: tmp/a.py:7: note: def __init__(self, x: int) -> None -tmp/a.py:7: note: @overload tmp/a.py:7: note: def __init__(self, x: str) -> None [case testSerialize__new__] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 4f2dd6651437..94d70c2bde24 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -907,16 +907,12 @@ from mypy_extensions import TypedDict D = TypedDict('D', {'x': int, 'y': str}) d: D d.get() # E: All overload variants of "get" of "Mapping" require at least one argument \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def get(self, k: str) -> object \ - # N: @overload \ # N: def [V] get(self, k: str, default: object) -> object d.get('x', 1, 2) # E: No overload variant of "get" of "Mapping" matches argument types "str", "int", "int" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def get(self, k: str) -> object \ - # N: @overload \ # N: def [V] get(self, k: str, default: Union[int, V]) -> object x = d.get('z') # E: TypedDict "D" has no key 'z' reveal_type(x) # E: Revealed type is 'Any' @@ -1216,10 +1212,8 @@ def f(x): pass a: A f(a) # E: No overload variant of "f" matches argument type "A" \ - # N: Possible overload variant(s): \ - # N: @overload \ + # N: Possible overload variants: \ # N: def f(x: str) -> None \ - # N: @overload \ # N: def f(x: int) -> None [builtins fixtures/dict.pyi] [typing fixtures/typing-full.pyi] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index c61d844a9859..7a92b715ed56 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -2113,10 +2113,8 @@ main:3: error: Revealed type is 'builtins.int' == main:3: error: Revealed type is 'Any' main:3: error: No overload variant of "foo" of "Wrapper" matches argument type "int" -main:3: note: Possible overload variant(s): -main:3: note: @overload +main:3: note: Possible overload variants: main:3: note: def foo(cls: Wrapper, x: int) -> int -main:3: note: @overload main:3: note: def foo(cls: Wrapper, x: str) -> str [case testRefreshGenericClass] @@ -6339,10 +6337,8 @@ def f(x): [out] == main:3: error: No overload variant of "f" matches argument type "str" -main:3: note: Possible overload variant(s): -main:3: note: @overload +main:3: note: Possible overload variants: main:3: note: def f(x: int) -> None -main:3: note: @overload main:3: note: def f(x: C) -> int [case testOverloadsDeleted] @@ -6453,10 +6449,8 @@ T = TypeVar('T', bound=str) [out] == a.py:2: error: No overload variant of "f" matches argument type "int" -a.py:2: note: Possible overload variant(s): -a.py:2: note: @overload +a.py:2: note: Possible overload variants: a.py:2: note: def f(x: C) -> None -a.py:2: note: @overload a.py:2: note: def [c.T <: str] f(x: c.T) -> c.T [case testOverloadsGenericToNonGeneric] @@ -6483,10 +6477,8 @@ class T: pass [out] == a.py:2: error: No overload variant of "f" matches argument type "int" -a.py:2: note: Possible overload variant(s): -a.py:2: note: @overload +a.py:2: note: Possible overload variants: a.py:2: note: def f(x: C) -> None -a.py:2: note: @overload a.py:2: note: def f(x: T) -> T [case testOverloadsToNonOverloaded] @@ -6643,10 +6635,9 @@ class C: [out] == a.py:2: error: No overload variant of "B" matches argument type "int" -a.py:2: note: Possible overload variant(s): -a.py:2: note: @overload +a.py:2: note: Possible overload variant: a.py:2: note: def __init__(self, x: str) -> B -a.py:2: note: <1 more overload(s) not shown> +a.py:2: note: <1 more non-matching overload not shown> [case testOverloadedToNormalMethodMetaclass] import a diff --git a/test-data/unit/python2eval.test b/test-data/unit/python2eval.test index d1c9fb381d3d..c891700c7e32 100644 --- a/test-data/unit/python2eval.test +++ b/test-data/unit/python2eval.test @@ -320,10 +320,8 @@ def f(x): # type: (unicode) -> int pass [out] _program.py:2: error: No overload variant of "f" matches argument type "int" -_program.py:2: note: Possible overload variant(s): -_program.py:2: note: @overload +_program.py:2: note: Possible overload variants: _program.py:2: note: def f(x: bytearray) -> int -_program.py:2: note: @overload _program.py:2: note: def f(x: unicode) -> int [case testByteArrayStrCompatibility_python2] diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 120e8457f3e3..544dc8fd1007 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1045,10 +1045,8 @@ _testTypedDictGet.py:7: error: Revealed type is 'builtins.int' _testTypedDictGet.py:8: error: Revealed type is 'builtins.str' _testTypedDictGet.py:9: error: TypedDict "D" has no key 'z' _testTypedDictGet.py:10: error: All overload variants of "get" of "Mapping" require at least one argument -_testTypedDictGet.py:10: note: Possible overload variant(s): -_testTypedDictGet.py:10: note: @overload +_testTypedDictGet.py:10: note: Possible overload variants: _testTypedDictGet.py:10: note: def get(self, k: str) -> object -_testTypedDictGet.py:10: note: @overload _testTypedDictGet.py:10: note: def [_T] get(self, k: str, default: object) -> object _testTypedDictGet.py:12: error: Revealed type is 'builtins.object*' From 873521b7d941b369719aab83c2ec807f4c2888c2 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Thu, 5 Jul 2018 20:51:18 -0700 Subject: [PATCH 5/5] Respond to the 3rd? 4th? 5th? code review(s) --- mypy/messages.py | 21 +++++++------- test-data/unit/check-overloading.test | 42 ++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 9cb5a5fe14e9..1e8d8d529e57 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1238,17 +1238,13 @@ 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, func: Overloaded, context: Context, + def pretty_overload(self, tp: Overloaded, context: Context, offset: int, max_items: int) -> None: - targets = func.items() - for item in targets[:max_items]: + for item in tp.items()[:max_items]: self.note('@overload', context, offset=2 * offset) self.note(self.pretty_callable(item), context, offset=2 * offset) - - max_available = len(targets) - shown = min(max_items, max_available) - if shown < max_available: - left = max_available - shown + left = len(tp.items()) - max_items + if left > 0: msg = '<{} more overload{} not shown>'.format(left, plural_s(left)) self.note(msg, context, offset=2 * offset) @@ -1265,14 +1261,19 @@ def pretty_overload_matches(self, max_matching = len(targets) max_available = len(func.items()) + # If there are 3 matches but max_items == 2, we might as well show + # all three items instead of having the 3rd item be an error message. + if shown + 1 == max_matching: + shown = max_matching + self.note('Possible overload variant{}:'.format(plural_s(shown)), context) - for item in targets[:max_items]: + for item in targets[:shown]: 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( + msg = '<{} more similar overload{} not shown, out of {} total overloads>'.format( left, plural_s(left), max_available) self.note(msg, context, offset=2 * offset) elif shown == max_matching < max_available: diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 2dcd5e85c573..7e388cbaa373 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -2229,7 +2229,7 @@ foo(*y) # E: No overload variant of "foo" matches argument type "List[str]" \ # N: Possible overload variants: \ # N: def foo(x: int, y: int, z: int, *args: int) -> C \ # N: def foo(x: int) -> A \ - # N: <1 more matching overload not shown, out of 3 total overloads> + # N: def foo(x: int, y: int) -> B [builtins fixtures/list.pyi] [case testOverloadMultipleVarargDefinition] @@ -4038,3 +4038,43 @@ main:11: error: Argument 6 to "f" has incompatible type "Union[int, str]"; expec main:11: error: Argument 7 to "f" has incompatible type "Union[int, str]"; expected "int" main:11: error: Argument 8 to "f" has incompatible type "Union[int, str]"; expected "int" main:11: note: Not all union combinations were tried because there are too many unions + +[case testOverloadErrorMessageManyMatches] +from typing import overload + +class A: pass +class B: pass +class C: pass +class D: pass + +@overload +def f(x: A) -> None: ... +@overload +def f(x: B) -> None: ... +@overload +def f(x: C) -> None: ... +@overload +def f(x: D) -> None: ... +@overload +def f(x: int, y: int) -> None: ... +def f(*args): pass + +f(3) # E: No overload variant of "f" matches argument type "int" \ + # N: Possible overload variants: \ + # N: def f(x: A) -> None \ + # N: def f(x: B) -> None \ + # N: <2 more similar overloads not shown, out of 5 total overloads> + +@overload +def g(x: A) -> None: ... +@overload +def g(x: B) -> None: ... +@overload +def g(x: C) -> None: ... +def g(*args): pass + +g(3) # E: No overload variant of "g" matches argument type "int" \ + # N: Possible overload variants: \ + # N: def g(x: A) -> None \ + # N: def g(x: B) -> None \ + # N: def g(x: C) -> None