Operating system: Fedora 27 4.13.9-300.fc27.x86_64)
Python version: 3.6.2
Black version: 18.5b1
Does also happen on master: yes
PEP484 was amended (https://github.com/python/typing/issues/186) to extend Python 2 compatible type annotation comments to allow placing each function parameter on a separate line with its own type annotation and the return type at the end. The motivation for this was to be able to write type annotations which would otherwise extend beyond the maximum line limit. But when it will fit in the maximum line length, Black combines all the arguments onto one line, including the type annotations, which become malformed.
Given this input
def f(a, # type: int
b, # type: str
c, # type: bool
): # type: (...) -> None
...
Black produces this
def f(a, b, c): # type: int # type: str # type: bool # type: (...) -> None
...
which has a malformed type annotation.
I think the correct behaviour would be either leave the function parameters on separate lines (do nothing) or restructure the type annotation like this:
def f(a, b, c): # (int, str, bool) -> None
...
or if the resultant line is too long, this
def f(a, b, c):
# (int, str, bool) -> None
...
This might deserve a separate issue, but it'd be nice if Black did the reverse as well: given a very long function type annotation on one line, annotate each parameter separately.
Yes, this is unfortunate and I should probably have special handling for it. Not quite clear how I would special-case it yet.
Noting that this is a more general issue with inline comments. For example, black changes the meaning of this comment intended to be associated with a particular kwarg:
$ cat example.py
myfunc(
a,
b=1, # this is critical due to #123
c=None)
$ black example.py
reformatted example.py
$ cat example.py
myfunc(a, b=1, c=None) # this is critical due to #123
@perrygeo, that's intentional.
There are many cases where neither of the suggestions (apart from 'do nothing') from @ned will work without making a line that is too long. Usually when I use a multiline type annotation it's because it won't fit on a single line after the function. That is,
def f(a, b, c):
# (int, str, bool) -> None
in some cases would cause the type annotation # (int, str, bool) -> None to extend beyond the character limit. In these cases, Black should probably do nothing at all.
I just ran into this as well. We're still on Python 2 (just a little bit longer now…) and I'd like to adopt Black, but this is a showstopper.
Thinking about it more, I wonder if maybe Black shouldn't be moving comments inside lists at all. Aside from that pattern being used for human-readable comments (perrygeo's example above), it is also universal among tools that work with Python code:
mypy:
def func(
arg1, # type: int
arg2, # type: int
):
flake8:
foo(
f(),
g(), # noqa: E123
):
pylint:
obj.things = [
a.b,
c.d, # pylint: disable=no-member
]
isort:
from os import (
path, # isort:skip
setenv,
getenv,
)
coverage.py:
foo(
x() if cover_me else y(),
a() if ignore_me else b(), # pragma: no cover
)
In each of these cases, moving the comment away from its line would either weaken the intended semantics or break things entirely. So maybe this issue is a little further-reaching than just Python 2 + mypy.
A small plug for #642 -- until this issue is fixed we won't be able to use black at Dropbox. We use this syntax extensively.
Fixed by #642.
Most helpful comment
Noting that this is a more general issue with inline comments. For example,
blackchanges the meaning of this comment intended to be associated with a particular kwarg: