Skip to content

Commit 48f9fc5

Browse files
authored
Hint at argument names when formatting callables with compatible return types in error messages (#18495)
Fixes #18493. Improves message in #12013 and #4530, but probably still doesn't make it clear enough. Use higher verbosity for type formatting in error message if callables' return types are compatible and supertype has some named arguments, as that is a popular source of confusion.
1 parent 878d892 commit 48f9fc5

File tree

2 files changed

+71
-1
lines changed

2 files changed

+71
-1
lines changed

mypy/messages.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2855,7 +2855,29 @@ def format_type_distinctly(*types: Type, options: Options, bare: bool = False) -
28552855
quoting them (such as prepending * or **) should use this.
28562856
"""
28572857
overlapping = find_type_overlaps(*types)
2858-
for verbosity in range(2):
2858+
2859+
def format_single(arg: Type) -> str:
2860+
return format_type_inner(arg, verbosity=0, options=options, fullnames=overlapping)
2861+
2862+
min_verbosity = 0
2863+
# Prevent emitting weird errors like:
2864+
# ... has incompatible type "Callable[[int], Child]"; expected "Callable[[int], Parent]"
2865+
if len(types) == 2:
2866+
left, right = types
2867+
left = get_proper_type(left)
2868+
right = get_proper_type(right)
2869+
# If the right type has named arguments, they may be the reason for incompatibility.
2870+
# This excludes cases when right is Callable[[Something], None] without named args,
2871+
# because that's usually the right thing to do.
2872+
if (
2873+
isinstance(left, CallableType)
2874+
and isinstance(right, CallableType)
2875+
and any(right.arg_names)
2876+
and is_subtype(left, right, ignore_pos_arg_names=True)
2877+
):
2878+
min_verbosity = 1
2879+
2880+
for verbosity in range(min_verbosity, 2):
28592881
strs = [
28602882
format_type_inner(type, verbosity=verbosity, options=options, fullnames=overlapping)
28612883
for type in types

test-data/unit/check-functions.test

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3472,3 +3472,51 @@ class Qux(Bar):
34723472
def baz(self, x) -> None:
34733473
pass
34743474
[builtins fixtures/tuple.pyi]
3475+
3476+
[case testDistinctFormatting]
3477+
from typing import Awaitable, Callable, ParamSpec
3478+
3479+
P = ParamSpec("P")
3480+
3481+
class A: pass
3482+
class B(A): pass
3483+
3484+
def decorator(f: Callable[P, None]) -> Callable[[Callable[P, A]], None]:
3485+
return lambda _: None
3486+
3487+
def key(x: int) -> None: ...
3488+
def fn_b(b: int) -> B: ...
3489+
3490+
decorator(key)(fn_b) # E: Argument 1 has incompatible type "Callable[[Arg(int, 'b')], B]"; expected "Callable[[Arg(int, 'x')], A]"
3491+
3492+
def decorator2(f: Callable[P, None]) -> Callable[
3493+
[Callable[P, Awaitable[None]]],
3494+
Callable[P, Awaitable[None]],
3495+
]:
3496+
return lambda f: f
3497+
3498+
def key2(x: int) -> None:
3499+
...
3500+
3501+
@decorator2(key2) # E: Argument 1 has incompatible type "Callable[[Arg(int, 'y')], Coroutine[Any, Any, None]]"; expected "Callable[[Arg(int, 'x')], Awaitable[None]]"
3502+
async def foo2(y: int) -> None:
3503+
...
3504+
3505+
class Parent:
3506+
def method_without(self) -> "Parent": ...
3507+
def method_with(self, param: str) -> "Parent": ...
3508+
3509+
class Child(Parent):
3510+
method_without: Callable[["Child"], "Child"]
3511+
method_with: Callable[["Child", str], "Child"] # E: Incompatible types in assignment (expression has type "Callable[[str], Child]", base class "Parent" defined the type as "Callable[[Arg(str, 'param')], Parent]")
3512+
[builtins fixtures/tuple.pyi]
3513+
3514+
[case testDistinctFormattingUnion]
3515+
from typing import Callable, Union
3516+
from mypy_extensions import Arg
3517+
3518+
def f(x: Callable[[Arg(int, 'x')], None]) -> None: pass
3519+
3520+
y: Callable[[Union[int, str]], None]
3521+
f(y) # E: Argument 1 to "f" has incompatible type "Callable[[Union[int, str]], None]"; expected "Callable[[Arg(int, 'x')], None]"
3522+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)