python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.2k stars 2.78k forks source link

Use MessageBuilder.pretty_callable in more places #5490

Open ilevkivskyi opened 6 years ago

ilevkivskyi commented 6 years ago

Currently callable types in many error messages are formatted as

Callable[[Arg('x', int), VarArg(str)], int]

We should probably switch to more "native" format that is already used in some errors (e.g. for protocol and overloads):

def (x: int, *args: str) -> int

The main obstacle here is probably updating all tests that use the old format.

This is a follow up for https://github.com/python/mypy/pull/5463/

ilevkivskyi commented 6 years ago

Another thing to note here is that maybe we need to trim horizontally long signatures anyway. Some complex overloads in typeshed can produce huge error messages.

JukkaL commented 2 years ago

Increasing priority since Arg(...) and VarArg(...) can look quite confusing and ugly in error messages.

I think that we can always use the "native" format for complex callable types. We'd then only use Callable[...] in messages if we can use one of these variants:

For everything else we can probably use the more readable def (...) -> t syntax. We should never need Arg(...), etc.

The relevant code is in mypy.messages.format_type_inner. If anybody would like to contribute a PR, I'm happy to review it (feel free to cc me).

JukkaL commented 1 year ago

12037 implements this, but needs some conflicts fixed. Anybody want to help with this?

sergab11 commented 1 year ago

Hey, @JukkaL , I'm a newby in contributing to Mypy, so correct me if I'm wrong... I thought about a solution. The changes involve the mypy.messages.format_callable_args:

def format_callable_args(
tp: Type,
format: Callable[[Type], str], 
verbosity: int,
) -> str:
 """Format a bunch of Callable arguments into a string"""
 arg_strings = []
 is_only_callable = False
 has_non_positional_arguments = False

 arg_names = []
 arg_types = []
 arg_kinds = []

 if isinstance(tp, ParamSpecType):
      arg_names = tp.prefix.arg_names
      arg_types = tp.prefix.arg_types
      arg_kinds = tp.prefix.arg_kinds
 else:
      is_only_callable = True
      arg_names = tp.arg_names
      arg_types = tp.arg_types
      arg_kinds = tp.arg_kinds

 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():
         arg_strings.append(format(arg_type))
     else:
         has_non_positional_arguments = True
         constructor = ARG_CONSTRUCTOR_NAMES[arg_kind]
         if arg_kind.is_star() or arg_name is None:
             arg_strings.append(f"{constructor}({format(arg_type)})")
         else:
             arg_strings.append(f"{constructor}({format(arg_type)}, {repr(arg_name)})")

 if is_only_callable and has_non_positional_arguments:
     return "pretty callable"

 return ", ".join(arg_strings)

And in the mypy.messages.format_type_inner (in the isinstance(func, CallableType): clause):

  args = format_callable_args(
                func, format, verbosity
              )
  if args == "pretty callable": 
     return pretty_callable(func)
  return f"Callable[[{args}], {return_type}]"

Thank you for your attention!!