Newlines within triple-quoted type expressions

If a type annotation is quoted using triple quotes, how should newlines be treated?

Pyright treats the entire triple-quoted expression as though it’s implicitly parenthesized and parses it as such. That means it accepts the following:

x: """
    int |
    str
"""

When I wrote that code in pyright, my thinking was that the meaning is unambiguous, so it doesn’t make sense to disallow it.

Mypy doesn’t accept this, but it does accept (and ignore) initial newlines, so it supports this:

x: """


    int | str
"""

Pyre doesn’t accept either of the above, but it does accept trailing newlines:

x: """   int | str
"""

Questions for the community:

  1. Is this important enough to standardize, or is it OK for type checkers to differ here?
  2. If it should be standardized, how should it be specified?
2 Likes

I think it makes sense to specify what’s allowed here.

Even though my personal preference would be to disallow triple quoted strings for type annotations entirely in favor of parenthesized string segments split over multiple lines, that sort of preference feels more like a style guideline, so in the realm of linters.

I think pyright’s current behavior is definitely the most user-friendly, it’s not that hard to write a parser for it either, in fact just statically wrapping the expression in parentheses before parsing it should always result in a valid expression that can be parsed by the regular Python parser, but there’s of course other fairly simple algorithms to convert it to a single line annotation.

1 Like

IMO multiline annotations should be supported. A collection of callable types that can be vertically aligned would be much more readable.

  1. It seems a bit silly to differ here, but if standardization is hard, having a common line on what to do when you want multiline annotations seems good.
  2. A simple “standard” would be “whitespace in annotation strings is not significant”, so normalization could just be re.sub("\w+", " ", annotation).
2 Likes

I don’t think that is save because an annotation might contain Literal, i.e. Literal[" ", " ", " ", " "].

Wait what? Apparently discourse doesn’t consider repeated spaces as significant… Or the preview at least doesn’t

Edit: yeag, discourse collapses repeated spaces inside of inline code blocks. Attempt with multiline code blocks:

Literal[" ",  "  ", "   ", "    "]

This at least works.

2 Likes

That’s what I get for thinking it through for half a minute. Good spot.

Require indentation, to force the hints to be written nicely?

u: """ int | str """  # OK
w: """ int | str
"""  # OK
x: """
    int |
    str
"""  # OK
y: """ int |
    str |
    float
"""  # OK
z: """
    int |
str |
        float
"""  # Not OK

That’s a linter’s job, not a type checker’s.

7 Likes

Adding parens is what the old f-string code did, and I don’t recall any complaints about it. So I’d suggest that.

Since this is something that users will try, I think this should be standardized. pyright’s interpretation seems sensible to me, although I’d slightly prefer triple quoted to be just disallowed. Quoted annotations are a bit of a dated concept anyway, thanks to PEP 563 and its eventual successor, and the only mention of quotes in the typing spec is this sentence:

(Note that the type annotation must be enclosed in quotes, making it a “forward reference”, to hide the expensive_mod reference from the interpreter runtime. In the variable annotation no quotes are needed.)

Exactly the same as they would be in an unquoted annotation. Because quoting an annotation is “simply” a way to hide it from the runtime, which might not support the constructs being used. I say this, but I was unable to find the specification for what quoting annotations means - if someone can point me to the actual spec, I’ll read it and see if that changes my view. But in the absence of an explicit statement that quoted annotations behave differently, I’d expect exactly the same behaviour, quoted or not.

And even if there is something explicit, I’d question whether it’s the right thing to do - having special cases like that which violate reasonable user expectations are just another situation where we end up making typing rules harder than they need to be, for no particular user benefit.

1 Like

I opened a PR changing mypy to accept your first example: Accept multiline quoted annotations by hauntsaninja · Pull Request #16765 · python/mypy · GitHub

Note mypy will already accept:

x: """(
    int |
    str
)
"""

If we standardise this, maybe we could specify this as follows:

Quoted type annotations should be parsed as if surrounded by parentheses.

My general inclination with the spec is to avoid forbidding things that are unambiguous and not too costly, even if somewhat niche (like this).

Exactly the same as they would be in an unquoted annotation.

Technically it’s not quite clear what this would mean. If you literally just removed the quotes in Eric’s example, you get a SyntaxError. It’s not unreasonable to view the quotes as parentheses-like in this context, which mirrors my proposed wording above.

6 Likes

If user’s expectations are for triple-quoted expressions to be treated exactly like if there were no quotes at all, then automatically wrapping doesn’t break anything, user can still additionally wrap their expression in parentheses. For other users, automatic parentheses are simply convenient.

I agree it’s good to standardize this, as it’s an area where type checkers are otherwise likely to diverge, but where users wouldn’t expect what they’re doing to only work on one type checker.

And pyright’s behavior of implicit parenthesization seems like the most sensible approach, so I would support standardizing to that behavior.

PEP 563/649 will definitely make quoted annotations less prevalent, but there will still be contexts where you need them, e.g. the argument to cast(). It’s a good general goal to make it so that quoted annotations are only rarely necessary, but for now, they’re here to stay, and we should specify their behavior well.

6 Likes

This change has been accepted by the typing council. The typing spec and conformance tests have been updated accordingly.

3 Likes