black icon indicating copy to clipboard operation
black copied to clipboard

Format string annotations and type aliases

Open Phlogistique opened this issue 3 years ago • 1 comments

Python type checkers supports enclosing type annotations in a string to make forward references possible. Although from __future__ import annotations makes that unnecessary in type annotations, it's still needed in type aliases.

Example:

# All these are equivalent from the point of view of the type checker 
x: "Union[A, B, C, D]"
x: Union["A", 'B', """C""", '''D''']
x: Union[A, B, C, D]

# Same here
y: TypeAlias = Union[A, B, C, D]
y: TypeAlias = "Union[A, B, C, D]"
y: TypeAlias = Union["A", "B", "C", "D"]

I would like Black to format string-enclosed type annotations like if they were not string-enclosed.

Describe the solution you'd like

Format

annotation: "Union[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]"
alias: TypeAlias =  "Union[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]"

to

annotation: """
    Union[
        aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, 
        aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
    ]
"""
alias: TypeAlias =  """
    Union[
        aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, 
        aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
    ]
"""

Or maybe

annotation: (
    "Union["
    "    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,"
    "    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,"
    "]"
)

Describe alternatives you've considered

Black could detect what parts of a type are forward references and unstringify all the rest:

For example it could format

annotation: "Union[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]"

to

annotation: Union[
    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
]

Note that in some cases the outer type of a generic type can be a forward reference, thus making this strategy impossible

annotation: TypeAlias = "ForwardRefToGenericType[int, str, None]" # cannot be unstringified

But my understanding is that Black does not have access to the information necessary to do this, so this is probably not possible.

Alternatively, Black could entirely stringify any type that has a string in it, if that string is not a Literal - BUT in the case of not using from __future__ import annotations, and in the case of type aliases, this could have a runtime impact.

Other context

The distinction between type aliases and normal variables is tricky, see https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases

Phlogistique avatar May 11 '22 07:05 Phlogistique

This is a reasonable request, but such changes will produce changes to the AST, which goes against Black's general safety guarantee. We already make similar changes in docstrings, but it's harder to reliably recognize type annotations than docstrings.

JelleZijlstra avatar May 11 '22 17:05 JelleZijlstra