Skip to content

Commit 3174d3f

Browse files
Use pretty_callable more often for callable expressions (#20128)
Fixes #5490 Uses pretty_callable for formatting Callable expressions that would otherwise be formatted with complex Args/VarArgs. Avoids pretty_callable for things that would be formatted with only positional args such as `Callable[[X, ..., Y], Z]`
1 parent 7aed696 commit 3174d3f

14 files changed

+102
-78
lines changed

mypy/messages.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2530,6 +2530,15 @@ def quote_type_string(type_string: str) -> str:
25302530
return f'"{type_string}"'
25312531

25322532

2533+
def should_format_arg_as_type(arg_kind: ArgKind, arg_name: str | None, verbosity: int) -> bool:
2534+
"""
2535+
Determine whether a function argument should be formatted as its Type or with name.
2536+
"""
2537+
return (arg_kind == ARG_POS and arg_name is None) or (
2538+
verbosity == 0 and arg_kind.is_positional()
2539+
)
2540+
2541+
25332542
def format_callable_args(
25342543
arg_types: list[Type],
25352544
arg_kinds: list[ArgKind],
@@ -2540,7 +2549,7 @@ def format_callable_args(
25402549
"""Format a bunch of Callable arguments into a string"""
25412550
arg_strings = []
25422551
for arg_name, arg_type, arg_kind in zip(arg_names, arg_types, arg_kinds):
2543-
if arg_kind == ARG_POS and arg_name is None or verbosity == 0 and arg_kind.is_positional():
2552+
if should_format_arg_as_type(arg_kind, arg_name, verbosity):
25442553
arg_strings.append(format(arg_type))
25452554
else:
25462555
constructor = ARG_CONSTRUCTOR_NAMES[arg_kind]
@@ -2558,13 +2567,18 @@ def format_type_inner(
25582567
options: Options,
25592568
fullnames: set[str] | None,
25602569
module_names: bool = False,
2570+
use_pretty_callable: bool = True,
25612571
) -> str:
25622572
"""
25632573
Convert a type to a relatively short string suitable for error messages.
25642574
25652575
Args:
2576+
typ: type to be formatted
25662577
verbosity: a coarse grained control on the verbosity of the type
2578+
options: Options object controlling formatting
25672579
fullnames: a set of names that should be printed in full
2580+
module_names: whether to show module names for module types
2581+
use_pretty_callable: use pretty_callable to format Callable types.
25682582
"""
25692583

25702584
def format(typ: Type) -> str:
@@ -2761,6 +2775,16 @@ def format_literal_value(typ: LiteralType) -> str:
27612775
param_spec = func.param_spec()
27622776
if param_spec is not None:
27632777
return f"Callable[{format(param_spec)}, {return_type}]"
2778+
2779+
# Use pretty format (def-style) for complex signatures with named, optional, or star args.
2780+
# Use compact Callable[[...], ...] only for signatures with all simple positional args.
2781+
if use_pretty_callable:
2782+
if any(
2783+
not should_format_arg_as_type(kind, name, verbosity)
2784+
for kind, name in zip(func.arg_kinds, func.arg_names)
2785+
):
2786+
return pretty_callable(func, options)
2787+
27642788
args = format_callable_args(
27652789
func.arg_types, func.arg_kinds, func.arg_names, format, verbosity
27662790
)

test-data/unit/check-assert-type-fail.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def f(si: arr.array[int]):
3030
[case testAssertTypeFailCallableArgKind]
3131
from typing import assert_type, Callable
3232
def myfunc(arg: int) -> None: pass
33-
assert_type(myfunc, Callable[[int], None]) # E: Expression is of type "Callable[[Arg(int, 'arg')], None]", not "Callable[[int], None]"
33+
assert_type(myfunc, Callable[[int], None]) # E: Expression is of type "def myfunc(arg: int) -> None", not "Callable[[int], None]"
3434

3535
[case testAssertTypeOverload]
3636
from typing import assert_type, overload

test-data/unit/check-callable.test

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -654,13 +654,13 @@ class Call(Protocol):
654654

655655
def f1() -> None: ...
656656
a1: Call = f1 # E: Incompatible types in assignment (expression has type "Callable[[], None]", variable has type "Call") \
657-
# N: "Call.__call__" has type "Callable[[Arg(int, 'x'), VarArg(Any), KwArg(Any)], None]"
657+
# N: "Call.__call__" has type "def __call__(self, x: int, *args: Any, **kwargs: Any) -> None"
658658
def f2(x: str) -> None: ...
659659
a2: Call = f2 # E: Incompatible types in assignment (expression has type "Callable[[str], None]", variable has type "Call") \
660-
# N: "Call.__call__" has type "Callable[[Arg(int, 'x'), VarArg(Any), KwArg(Any)], None]"
660+
# N: "Call.__call__" has type "def __call__(self, x: int, *args: Any, **kwargs: Any) -> None"
661661
def f3(y: int) -> None: ...
662662
a3: Call = f3 # E: Incompatible types in assignment (expression has type "Callable[[int], None]", variable has type "Call") \
663-
# N: "Call.__call__" has type "Callable[[Arg(int, 'x'), VarArg(Any), KwArg(Any)], None]"
663+
# N: "Call.__call__" has type "def __call__(self, x: int, *args: Any, **kwargs: Any) -> None"
664664
def f4(x: int) -> None: ...
665665
a4: Call = f4
666666

test-data/unit/check-functions.test

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -107,30 +107,30 @@ if int():
107107
[case testSubtypingFunctionsDoubleCorrespondence]
108108
def l(x) -> None: ...
109109
def r(__x, *, x) -> None: ...
110-
r = l # E: Incompatible types in assignment (expression has type "Callable[[Any], None]", variable has type "Callable[[Any, NamedArg(Any, 'x')], None]")
110+
r = l # E: Incompatible types in assignment (expression has type "Callable[[Any], None]", variable has type "def r(Any, /, *, x: Any) -> None")
111111

112112
[case testSubtypingFunctionsDoubleCorrespondenceNamedOptional]
113113
def l(x) -> None: ...
114114
def r(__x, *, x = 1) -> None: ...
115-
r = l # E: Incompatible types in assignment (expression has type "Callable[[Any], None]", variable has type "Callable[[Any, DefaultNamedArg(Any, 'x')], None]")
115+
r = l # E: Incompatible types in assignment (expression has type "Callable[[Any], None]", variable has type "def r(Any, /, *, x: Any = ...) -> None")
116116

117117
[case testSubtypingFunctionsDoubleCorrespondenceBothNamedOptional]
118118
def l(x = 1) -> None: ...
119119
def r(__x, *, x = 1) -> None: ...
120-
r = l # E: Incompatible types in assignment (expression has type "Callable[[Any], None]", variable has type "Callable[[Any, DefaultNamedArg(Any, 'x')], None]")
120+
r = l # E: Incompatible types in assignment (expression has type "Callable[[Any], None]", variable has type "def r(Any, /, *, x: Any = ...) -> None")
121121

122122
[case testSubtypingFunctionsTrivialSuffixRequired]
123123
def l(__x) -> None: ...
124124
def r(x, *args, **kwargs) -> None: ...
125125

126-
r = l # E: Incompatible types in assignment (expression has type "Callable[[Any], None]", variable has type "Callable[[Arg(Any, 'x'), VarArg(Any), KwArg(Any)], None]")
126+
r = l # E: Incompatible types in assignment (expression has type "Callable[[Any], None]", variable has type "def r(x: Any, *args: Any, **kwargs: Any) -> None")
127127
[builtins fixtures/dict.pyi]
128128

129129
[case testSubtypingFunctionsTrivialSuffixOptional]
130130
def l(__x = 1) -> None: ...
131131
def r(x = 1, *args, **kwargs) -> None: ...
132132

133-
r = l # E: Incompatible types in assignment (expression has type "Callable[[DefaultArg(Any)], None]", variable has type "Callable[[DefaultArg(Any, 'x'), VarArg(Any), KwArg(Any)], None]")
133+
r = l # E: Incompatible types in assignment (expression has type "def l(Any = ..., /) -> None", variable has type "def r(x: Any = ..., *args: Any, **kwargs: Any) -> None")
134134
[builtins fixtures/dict.pyi]
135135

136136
[case testSubtypingFunctionsRequiredLeftArgNotPresent]
@@ -170,13 +170,13 @@ if int():
170170
if int():
171171
ff_nonames = f_nonames # reset
172172
if int():
173-
ff = ff_nonames # E: Incompatible types in assignment (expression has type "Callable[[int, str], None]", variable has type "Callable[[Arg(int, 'a'), Arg(str, 'b')], None]")
173+
ff = ff_nonames # E: Incompatible types in assignment (expression has type "Callable[[int, str], None]", variable has type "def f(a: int, b: str) -> None")
174174
if int():
175175
ff = f # reset
176176
if int():
177-
gg = ff # E: Incompatible types in assignment (expression has type "Callable[[Arg(int, 'a'), Arg(str, 'b')], None]", variable has type "Callable[[Arg(int, 'a'), DefaultArg(str, 'b')], None]")
177+
gg = ff # E: Incompatible types in assignment (expression has type "def f(a: int, b: str) -> None", variable has type "def g(a: int, b: str = ...) -> None")
178178
if int():
179-
gg = hh # E: Incompatible types in assignment (expression has type "Callable[[Arg(int, 'aa'), DefaultArg(str, 'b')], None]", variable has type "Callable[[Arg(int, 'a'), DefaultArg(str, 'b')], None]")
179+
gg = hh # E: Incompatible types in assignment (expression has type "def h(aa: int, b: str = ...) -> None", variable has type "def g(a: int, b: str = ...) -> None")
180180

181181
[case testSubtypingFunctionsArgsKwargs]
182182
from typing import Any, Callable
@@ -245,7 +245,7 @@ gg = g
245245
if int():
246246
ff = g
247247
if int():
248-
gg = f # E: Incompatible types in assignment (expression has type "Callable[[int, str], None]", variable has type "Callable[[Arg(int, 'a'), Arg(str, 'b')], None]")
248+
gg = f # E: Incompatible types in assignment (expression has type "Callable[[int, str], None]", variable has type "def g(a: int, b: str) -> None")
249249

250250
[case testLackOfNamesFastparse]
251251
def f(__a: int, __b: str) -> None: pass
@@ -257,7 +257,7 @@ gg = g
257257
if int():
258258
ff = g
259259
if int():
260-
gg = f # E: Incompatible types in assignment (expression has type "Callable[[int, str], None]", variable has type "Callable[[Arg(int, 'a'), Arg(str, 'b')], None]")
260+
gg = f # E: Incompatible types in assignment (expression has type "Callable[[int, str], None]", variable has type "def g(a: int, b: str) -> None")
261261

262262
[case testFunctionTypeCompatibilityWithOtherTypes]
263263
# flags: --no-strict-optional
@@ -2016,12 +2016,12 @@ def isf_unnamed(__i: int, __s: str) -> str:
20162016

20172017
int_str_fun = isf
20182018
int_str_fun = isf_unnamed
2019-
int_named_str_fun = isf_unnamed # E: Incompatible types in assignment (expression has type "Callable[[int, str], str]", variable has type "Callable[[int, Arg(str, 's')], str]")
2019+
int_named_str_fun = isf_unnamed # E: Incompatible types in assignment (expression has type "Callable[[int, str], str]", variable has type "def (int, /, s: str) -> str")
20202020
int_opt_str_fun = iosf
20212021
int_str_fun = iosf
2022-
int_opt_str_fun = isf # E: Incompatible types in assignment (expression has type "Callable[[Arg(int, 'ii'), Arg(str, 'ss')], str]", variable has type "Callable[[int, DefaultArg(str)], str]")
2022+
int_opt_str_fun = isf # E: Incompatible types in assignment (expression has type "def isf(ii: int, ss: str) -> str", variable has type "def (int, str = ..., /) -> str")
20232023

2024-
int_named_str_fun = isf # E: Incompatible types in assignment (expression has type "Callable[[Arg(int, 'ii'), Arg(str, 'ss')], str]", variable has type "Callable[[int, Arg(str, 's')], str]")
2024+
int_named_str_fun = isf # E: Incompatible types in assignment (expression has type "def isf(ii: int, ss: str) -> str", variable has type "def (int, /, s: str) -> str")
20252025
int_named_str_fun = iosf
20262026

20272027
[builtins fixtures/dict.pyi]
@@ -2076,7 +2076,7 @@ def g4(*, y: int) -> str: pass
20762076
f(g1)
20772077
f(g2)
20782078
f(g3)
2079-
f(g4) # E: Argument 1 to "f" has incompatible type "Callable[[NamedArg(int, 'y')], str]"; expected "Callable[..., int]"
2079+
f(g4) # E: Argument 1 to "f" has incompatible type "def g4(*, y: int) -> str"; expected "Callable[..., int]"
20802080

20812081
[case testCallableWithArbitraryArgsSubtypingWithGenericFunc]
20822082
from typing import Callable, TypeVar
@@ -2238,7 +2238,7 @@ def g(x, y): pass
22382238
def h(x): pass
22392239
def j(y) -> Any: pass
22402240
f = h
2241-
f = j # E: Incompatible types in assignment (expression has type "Callable[[Arg(Any, 'y')], Any]", variable has type "Callable[[Arg(Any, 'x')], Any]")
2241+
f = j # E: Incompatible types in assignment (expression has type "def j(y: Any) -> Any", variable has type "def f(x: Any) -> Any")
22422242
f = g # E: Incompatible types in assignment (expression has type "Callable[[Any, Any], Any]", variable has type "Callable[[Any], Any]")
22432243

22442244
[case testRedefineFunction2]
@@ -3531,7 +3531,7 @@ def decorator(f: Callable[P, None]) -> Callable[[Callable[P, A]], None]:
35313531
def key(x: int) -> None: ...
35323532
def fn_b(b: int) -> B: ...
35333533

3534-
decorator(key)(fn_b) # E: Argument 1 has incompatible type "Callable[[Arg(int, 'b')], B]"; expected "Callable[[Arg(int, 'x')], A]"
3534+
decorator(key)(fn_b) # E: Argument 1 has incompatible type "def fn_b(b: int) -> B"; expected "def (x: int) -> A"
35353535

35363536
def decorator2(f: Callable[P, None]) -> Callable[
35373537
[Callable[P, Awaitable[None]]],
@@ -3542,7 +3542,7 @@ def decorator2(f: Callable[P, None]) -> Callable[
35423542
def key2(x: int) -> None:
35433543
...
35443544

3545-
@decorator2(key2) # E: Argument 1 has incompatible type "Callable[[Arg(int, 'y')], Coroutine[Any, Any, None]]"; expected "Callable[[Arg(int, 'x')], Awaitable[None]]"
3545+
@decorator2(key2) # E: Argument 1 has incompatible type "def foo2(y: int) -> Coroutine[Any, Any, None]"; expected "def (x: int) -> Awaitable[None]"
35463546
async def foo2(y: int) -> None:
35473547
...
35483548

@@ -3552,7 +3552,7 @@ class Parent:
35523552

35533553
class Child(Parent):
35543554
method_without: Callable[[], "Child"]
3555-
method_with: Callable[[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]")
3555+
method_with: Callable[[str], "Child"] # E: Incompatible types in assignment (expression has type "Callable[[str], Child]", base class "Parent" defined the type as "def method_with(self, param: str) -> Parent")
35563556
[builtins fixtures/tuple.pyi]
35573557

35583558
[case testDistinctFormattingUnion]
@@ -3562,7 +3562,7 @@ from mypy_extensions import Arg
35623562
def f(x: Callable[[Arg(int, 'x')], None]) -> None: pass
35633563

35643564
y: Callable[[Union[int, str]], None]
3565-
f(y) # E: Argument 1 to "f" has incompatible type "Callable[[Union[int, str]], None]"; expected "Callable[[Arg(int, 'x')], None]"
3565+
f(y) # E: Argument 1 to "f" has incompatible type "Callable[[Union[int, str]], None]"; expected "def (x: int) -> None"
35663566
[builtins fixtures/tuple.pyi]
35673567

35683568
[case testAbstractOverloadsWithoutImplementationAllowed]

test-data/unit/check-functools.test

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def takes_callable_int(f: Callable[..., int]) -> None: ...
162162
def takes_callable_str(f: Callable[..., str]) -> None: ...
163163
takes_callable_int(p1)
164164
takes_callable_str(p1) # E: Argument 1 to "takes_callable_str" has incompatible type "partial[int]"; expected "Callable[..., str]" \
165-
# N: "partial[int].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], int]"
165+
# N: "partial[int].__call__" has type "def __call__(__self, *args: Any, **kwargs: Any) -> int"
166166

167167
p2 = functools.partial(foo, 1)
168168
p2("a") # OK
@@ -386,7 +386,7 @@ q: partial[bool] = partial(generic, resulting_type=str) # E: Argument "resultin
386386

387387
pc: Callable[..., str] = partial(generic, resulting_type=str)
388388
qc: Callable[..., bool] = partial(generic, resulting_type=str) # E: Incompatible types in assignment (expression has type "partial[str]", variable has type "Callable[..., bool]") \
389-
# N: "partial[str].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], str]"
389+
# N: "partial[str].__call__" has type "def __call__(__self, *args: Any, **kwargs: Any) -> str"
390390
[builtins fixtures/tuple.pyi]
391391

392392
[case testFunctoolsPartialNestedPartial]
@@ -697,11 +697,11 @@ use_int_callable(partial(func_b, b=""))
697697
use_func_callable(partial(func_b, b=""))
698698
use_int_callable(partial(func_c, b=""))
699699
use_func_callable(partial(func_c, b=""))
700-
use_int_callable(partial(func_fn, b="")) # E: Argument 1 to "use_int_callable" has incompatible type "partial[Callable[[VarArg(Any), KwArg(Any)], Any]]"; expected "Callable[[int], int]" \
701-
# N: "partial[Callable[[VarArg(Any), KwArg(Any)], Any]].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], Callable[[VarArg(Any), KwArg(Any)], Any]]"
700+
use_int_callable(partial(func_fn, b="")) # E: Argument 1 to "use_int_callable" has incompatible type "partial[def (*Any, **Any) -> Any]"; expected "Callable[[int], int]" \
701+
# N: "partial[def (*Any, **Any) -> Any].__call__" has type "def __call__(__self, *args: Any, **kwargs: Any) -> def (*Any, **Any) -> Any"
702702
use_func_callable(partial(func_fn, b=""))
703-
use_int_callable(partial(func_fn_unpack, b="")) # E: Argument 1 to "use_int_callable" has incompatible type "partial[Callable[[VarArg(Any)], Any]]"; expected "Callable[[int], int]" \
704-
# N: "partial[Callable[[VarArg(Any)], Any]].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], Callable[[VarArg(Any)], Any]]"
703+
use_int_callable(partial(func_fn_unpack, b="")) # E: Argument 1 to "use_int_callable" has incompatible type "partial[def (*Any) -> Any]"; expected "Callable[[int], int]" \
704+
# N: "partial[def (*Any) -> Any].__call__" has type "def __call__(__self, *args: Any, **kwargs: Any) -> def (*Any) -> Any"
705705
use_func_callable(partial(func_fn_unpack, b=""))
706706

707707
# But we should not erase typevars that aren't bound by function
@@ -714,7 +714,7 @@ def outer_b(arg: Tb) -> None:
714714

715715
reveal_type(partial(inner, b="")) # N: Revealed type is "functools.partial[Tb`-1]"
716716
use_int_callable(partial(inner, b="")) # E: Argument 1 to "use_int_callable" has incompatible type "partial[Tb]"; expected "Callable[[int], int]" \
717-
# N: "partial[Tb].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], Tb]"
717+
# N: "partial[Tb].__call__" has type "def __call__(__self, *args: Any, **kwargs: Any) -> Tb"
718718

719719
def outer_c(arg: Tc) -> None:
720720

@@ -724,5 +724,5 @@ def outer_c(arg: Tc) -> None:
724724
reveal_type(partial(inner, b="")) # N: Revealed type is "functools.partial[builtins.int]" \
725725
# N: Revealed type is "functools.partial[builtins.str]"
726726
use_int_callable(partial(inner, b="")) # E: Argument 1 to "use_int_callable" has incompatible type "partial[str]"; expected "Callable[[int], int]" \
727-
# N: "partial[str].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], str]"
727+
# N: "partial[str].__call__" has type "def __call__(__self, *args: Any, **kwargs: Any) -> str"
728728
[builtins fixtures/tuple.pyi]

test-data/unit/check-incremental.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6950,7 +6950,7 @@ p3 = functools.partial(foo, b="a")
69506950
[out]
69516951
tmp/a.py:8: note: Revealed type is "functools.partial[builtins.int]"
69526952
tmp/a.py:13: error: Argument 1 to "takes_callable_str" has incompatible type "partial[int]"; expected "Callable[..., str]"
6953-
tmp/a.py:13: note: "partial[int].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], int]"
6953+
tmp/a.py:13: note: "partial[int].__call__" has type "def __call__(__self, *args: Any, **kwargs: Any) -> int"
69546954
tmp/a.py:18: error: Argument 1 to "foo" has incompatible type "int"; expected "str"
69556955
tmp/a.py:19: error: Too many arguments for "foo"
69566956
tmp/a.py:19: error: Argument 1 to "foo" has incompatible type "int"; expected "str"

0 commit comments

Comments
 (0)