Skip to content

Hint at argument names when formatting callables with compatible return types in error messages #18495

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

Merged
merged 4 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 23 additions & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 48 additions & 0 deletions test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Loading