diff --git a/mypy/messages.py b/mypy/messages.py index b63310825f7d7..8e614f02277a9 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -2855,7 +2855,29 @@ def format_type_distinctly(*types: Type, options: Options, bare: bool = False) - quoting them (such as prepending * or **) should use this. """ overlapping = find_type_overlaps(*types) - for verbosity in range(2): + + def format_single(arg: Type) -> str: + return format_type_inner(arg, verbosity=0, options=options, fullnames=overlapping) + + min_verbosity = 0 + # Prevent emitting weird errors like: + # ... has incompatible type "Callable[[int], Child]"; expected "Callable[[int], Parent]" + if len(types) == 2: + left, right = types + left = get_proper_type(left) + right = get_proper_type(right) + # If the right type has named arguments, they may be the reason for incompatibility. + # This excludes cases when right is Callable[[Something], None] without named args, + # because that's usually the right thing to do. + if ( + isinstance(left, CallableType) + and isinstance(right, CallableType) + and any(right.arg_names) + and is_subtype(left, right, ignore_pos_arg_names=True) + ): + min_verbosity = 1 + + for verbosity in range(min_verbosity, 2): strs = [ format_type_inner(type, verbosity=verbosity, options=options, fullnames=overlapping) for type in types diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 18425efb9cb08..58973307a1ae7 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -3472,3 +3472,51 @@ class Qux(Bar): def baz(self, x) -> None: pass [builtins fixtures/tuple.pyi] + +[case testDistinctFormatting] +from typing import Awaitable, Callable, ParamSpec + +P = ParamSpec("P") + +class A: pass +class B(A): pass + +def decorator(f: Callable[P, None]) -> Callable[[Callable[P, A]], None]: + return lambda _: None + +def key(x: int) -> None: ... +def fn_b(b: int) -> B: ... + +decorator(key)(fn_b) # E: Argument 1 has incompatible type "Callable[[Arg(int, 'b')], B]"; expected "Callable[[Arg(int, 'x')], A]" + +def decorator2(f: Callable[P, None]) -> Callable[ + [Callable[P, Awaitable[None]]], + Callable[P, Awaitable[None]], +]: + return lambda f: f + +def key2(x: int) -> None: + ... + +@decorator2(key2) # E: Argument 1 has incompatible type "Callable[[Arg(int, 'y')], Coroutine[Any, Any, None]]"; expected "Callable[[Arg(int, 'x')], Awaitable[None]]" +async def foo2(y: int) -> None: + ... + +class Parent: + def method_without(self) -> "Parent": ... + def method_with(self, param: str) -> "Parent": ... + +class Child(Parent): + method_without: Callable[["Child"], "Child"] + 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]") +[builtins fixtures/tuple.pyi] + +[case testDistinctFormattingUnion] +from typing import Callable, Union +from mypy_extensions import Arg + +def f(x: Callable[[Arg(int, 'x')], None]) -> None: pass + +y: Callable[[Union[int, str]], None] +f(y) # E: Argument 1 to "f" has incompatible type "Callable[[Union[int, str]], None]"; expected "Callable[[Arg(int, 'x')], None]" +[builtins fixtures/tuple.pyi]