Skip to content
Open
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
26 changes: 25 additions & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -2530,6 +2530,15 @@ def quote_type_string(type_string: str) -> str:
return f'"{type_string}"'


def should_format_arg_as_type(arg_kind: ArgKind, arg_name: str | None, verbosity: int) -> bool:
"""
Determine whether a function argument should be formatted as its Type or with name.
"""
return (arg_kind == ARG_POS and arg_name is None) or (
verbosity == 0 and arg_kind.is_positional()
)


def format_callable_args(
arg_types: list[Type],
arg_kinds: list[ArgKind],
Expand All @@ -2540,7 +2549,7 @@ def format_callable_args(
"""Format a bunch of Callable arguments into a string"""
arg_strings = []
for arg_name, arg_type, arg_kind in zip(arg_names, arg_types, arg_kinds):
if arg_kind == ARG_POS and arg_name is None or verbosity == 0 and arg_kind.is_positional():
if should_format_arg_as_type(arg_kind, arg_name, verbosity):
arg_strings.append(format(arg_type))
else:
constructor = ARG_CONSTRUCTOR_NAMES[arg_kind]
Expand All @@ -2558,13 +2567,18 @@ def format_type_inner(
options: Options,
fullnames: set[str] | None,
module_names: bool = False,
use_pretty_callable: bool = True,
) -> str:
"""
Convert a type to a relatively short string suitable for error messages.

Args:
typ: type to be formatted
verbosity: a coarse grained control on the verbosity of the type
options: Options object controlling formatting
fullnames: a set of names that should be printed in full
module_names: whether to show module names for module types
use_pretty_callable: use pretty_callable to format Callable types.
"""

def format(typ: Type) -> str:
Expand Down Expand Up @@ -2761,6 +2775,16 @@ def format_literal_value(typ: LiteralType) -> str:
param_spec = func.param_spec()
if param_spec is not None:
return f"Callable[{format(param_spec)}, {return_type}]"

# Use pretty format (def-style) for complex signatures with named, optional, or star args.
# Use compact Callable[[...], ...] only for signatures with all simple positional args.
would_use_arg_constructors = any(
not should_format_arg_as_type(kind, name, verbosity)
for kind, name in zip(func.arg_kinds, func.arg_names)
)
if use_pretty_callable and would_use_arg_constructors:
return pretty_callable(func, options)

args = format_callable_args(
func.arg_types, func.arg_kinds, func.arg_names, format, verbosity
)
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-assert-type-fail.test
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def f(si: arr.array[int]):
[case testAssertTypeFailCallableArgKind]
from typing import assert_type, Callable
def myfunc(arg: int) -> None: pass
assert_type(myfunc, Callable[[int], None]) # E: Expression is of type "Callable[[Arg(int, 'arg')], None]", not "Callable[[int], None]"
assert_type(myfunc, Callable[[int], None]) # E: Expression is of type "def myfunc(arg: int) -> None", not "Callable[[int], None]"

[case testAssertTypeOverload]
from typing import assert_type, overload
Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-callable.test
Original file line number Diff line number Diff line change
Expand Up @@ -654,13 +654,13 @@ class Call(Protocol):

def f1() -> None: ...
a1: Call = f1 # E: Incompatible types in assignment (expression has type "Callable[[], None]", variable has type "Call") \
# N: "Call.__call__" has type "Callable[[Arg(int, 'x'), VarArg(Any), KwArg(Any)], None]"
# N: "Call.__call__" has type "def __call__(self, x: int, *args: Any, **kwargs: Any) -> None"
def f2(x: str) -> None: ...
a2: Call = f2 # E: Incompatible types in assignment (expression has type "Callable[[str], None]", variable has type "Call") \
# N: "Call.__call__" has type "Callable[[Arg(int, 'x'), VarArg(Any), KwArg(Any)], None]"
# N: "Call.__call__" has type "def __call__(self, x: int, *args: Any, **kwargs: Any) -> None"
def f3(y: int) -> None: ...
a3: Call = f3 # E: Incompatible types in assignment (expression has type "Callable[[int], None]", variable has type "Call") \
# N: "Call.__call__" has type "Callable[[Arg(int, 'x'), VarArg(Any), KwArg(Any)], None]"
# N: "Call.__call__" has type "def __call__(self, x: int, *args: Any, **kwargs: Any) -> None"
def f4(x: int) -> None: ...
a4: Call = f4

Expand Down
38 changes: 19 additions & 19 deletions test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -107,30 +107,30 @@ if int():
[case testSubtypingFunctionsDoubleCorrespondence]
def l(x) -> None: ...
def r(__x, *, x) -> None: ...
r = l # E: Incompatible types in assignment (expression has type "Callable[[Any], None]", variable has type "Callable[[Any, NamedArg(Any, 'x')], None]")
r = l # E: Incompatible types in assignment (expression has type "Callable[[Any], None]", variable has type "def r(Any, /, *, x: Any) -> None")

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

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

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

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]")
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")
[builtins fixtures/dict.pyi]

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

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]")
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")
[builtins fixtures/dict.pyi]

[case testSubtypingFunctionsRequiredLeftArgNotPresent]
Expand Down Expand Up @@ -170,13 +170,13 @@ if int():
if int():
ff_nonames = f_nonames # reset
if int():
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]")
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")
if int():
ff = f # reset
if int():
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]")
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")
if int():
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]")
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")

[case testSubtypingFunctionsArgsKwargs]
from typing import Any, Callable
Expand Down Expand Up @@ -245,7 +245,7 @@ gg = g
if int():
ff = g
if int():
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]")
gg = f # E: Incompatible types in assignment (expression has type "Callable[[int, str], None]", variable has type "def g(a: int, b: str) -> None")

[case testLackOfNamesFastparse]
def f(__a: int, __b: str) -> None: pass
Expand All @@ -257,7 +257,7 @@ gg = g
if int():
ff = g
if int():
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]")
gg = f # E: Incompatible types in assignment (expression has type "Callable[[int, str], None]", variable has type "def g(a: int, b: str) -> None")

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

int_str_fun = isf
int_str_fun = isf_unnamed
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]")
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")
int_opt_str_fun = iosf
int_str_fun = iosf
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]")
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")

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]")
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")
int_named_str_fun = iosf

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

[case testCallableWithArbitraryArgsSubtypingWithGenericFunc]
from typing import Callable, TypeVar
Expand Down Expand Up @@ -2238,7 +2238,7 @@ def g(x, y): pass
def h(x): pass
def j(y) -> Any: pass
f = h
f = j # E: Incompatible types in assignment (expression has type "Callable[[Arg(Any, 'y')], Any]", variable has type "Callable[[Arg(Any, 'x')], Any]")
f = j # E: Incompatible types in assignment (expression has type "def j(y: Any) -> Any", variable has type "def f(x: Any) -> Any")
f = g # E: Incompatible types in assignment (expression has type "Callable[[Any, Any], Any]", variable has type "Callable[[Any], Any]")

[case testRedefineFunction2]
Expand Down Expand Up @@ -3531,7 +3531,7 @@ def decorator(f: Callable[P, None]) -> Callable[[Callable[P, A]], 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]"
decorator(key)(fn_b) # E: Argument 1 has incompatible type "def fn_b(b: int) -> B"; expected "def (x: int) -> A"

def decorator2(f: Callable[P, None]) -> Callable[
[Callable[P, Awaitable[None]]],
Expand All @@ -3542,7 +3542,7 @@ def decorator2(f: Callable[P, None]) -> Callable[
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]]"
@decorator2(key2) # E: Argument 1 has incompatible type "def foo2(y: int) -> Coroutine[Any, Any, None]"; expected "def (x: int) -> Awaitable[None]"
async def foo2(y: int) -> None:
...

Expand All @@ -3552,7 +3552,7 @@ class Parent:

class Child(Parent):
method_without: Callable[[], "Child"]
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]")
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")
[builtins fixtures/tuple.pyi]

[case testDistinctFormattingUnion]
Expand All @@ -3562,7 +3562,7 @@ 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]"
f(y) # E: Argument 1 to "f" has incompatible type "Callable[[Union[int, str]], None]"; expected "def (x: int) -> None"
[builtins fixtures/tuple.pyi]

[case testAbstractOverloadsWithoutImplementationAllowed]
Expand Down
16 changes: 8 additions & 8 deletions test-data/unit/check-functools.test
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def takes_callable_int(f: Callable[..., int]) -> None: ...
def takes_callable_str(f: Callable[..., str]) -> None: ...
takes_callable_int(p1)
takes_callable_str(p1) # E: Argument 1 to "takes_callable_str" has incompatible type "partial[int]"; expected "Callable[..., str]" \
# N: "partial[int].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], int]"
# N: "partial[int].__call__" has type "def __call__(__self, *args: Any, **kwargs: Any) -> int"

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

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

[case testFunctoolsPartialNestedPartial]
Expand Down Expand Up @@ -697,11 +697,11 @@ use_int_callable(partial(func_b, b=""))
use_func_callable(partial(func_b, b=""))
use_int_callable(partial(func_c, b=""))
use_func_callable(partial(func_c, b=""))
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]" \
# N: "partial[Callable[[VarArg(Any), KwArg(Any)], Any]].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], Callable[[VarArg(Any), KwArg(Any)], Any]]"
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]" \
# N: "partial[def (*Any, **Any) -> Any].__call__" has type "def __call__(__self, *args: Any, **kwargs: Any) -> def (*Any, **Any) -> Any"
use_func_callable(partial(func_fn, b=""))
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]" \
# N: "partial[Callable[[VarArg(Any)], Any]].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], Callable[[VarArg(Any)], Any]]"
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]" \
# N: "partial[def (*Any) -> Any].__call__" has type "def __call__(__self, *args: Any, **kwargs: Any) -> def (*Any) -> Any"
use_func_callable(partial(func_fn_unpack, b=""))

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

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

def outer_c(arg: Tc) -> None:

Expand All @@ -724,5 +724,5 @@ def outer_c(arg: Tc) -> None:
reveal_type(partial(inner, b="")) # N: Revealed type is "functools.partial[builtins.int]" \
# N: Revealed type is "functools.partial[builtins.str]"
use_int_callable(partial(inner, b="")) # E: Argument 1 to "use_int_callable" has incompatible type "partial[str]"; expected "Callable[[int], int]" \
# N: "partial[str].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], str]"
# N: "partial[str].__call__" has type "def __call__(__self, *args: Any, **kwargs: Any) -> str"
[builtins fixtures/tuple.pyi]
2 changes: 1 addition & 1 deletion test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
Expand Up @@ -6950,7 +6950,7 @@ p3 = functools.partial(foo, b="a")
[out]
tmp/a.py:8: note: Revealed type is "functools.partial[builtins.int]"
tmp/a.py:13: error: Argument 1 to "takes_callable_str" has incompatible type "partial[int]"; expected "Callable[..., str]"
tmp/a.py:13: note: "partial[int].__call__" has type "Callable[[VarArg(Any), KwArg(Any)], int]"
tmp/a.py:13: note: "partial[int].__call__" has type "def __call__(__self, *args: Any, **kwargs: Any) -> int"
tmp/a.py:18: error: Argument 1 to "foo" has incompatible type "int"; expected "str"
tmp/a.py:19: error: Too many arguments for "foo"
tmp/a.py:19: error: Argument 1 to "foo" has incompatible type "int"; expected "str"
Expand Down
Loading