python / mypy

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

Cannot ignore type errors in multiline strings #9484

Open wadetregaskis-linkedin opened 3 years ago

wadetregaskis-linkedin commented 3 years ago
stringy = "foo"

f"""\
{stringy.nope()}
"""  # type: ignore

(ignore that this is a true positive - my real code is much more complicated and is actually valid, the above is just a contrived example to show the core problem)

In Python 3.7 with mypy 0.782, it reports the error on the closing """ (if you remove the # type: ignore), so it picks up the ignore directive and all is well. In Python 3.8, it reports the error on the previous line. There's no way to add # type: ignore on that line, and adding it anywhere else has no effect.

In other cases it tends to report the error as being on the line with the opening """… I'm not sure why in this particular, contrived example it behaves differently. But either way, it's a major problem, since there's no practical workaround for this [in my much larger, real-world code]. It's blocking the upgrade to Python 3.8 for one of my major projects.

(the output from mypy w/ Python 3.8)

test.py:4: error: "str" has no attribute "nope"
Found 1 error in 1 file (checked 1 source file)
hauntsaninja commented 3 years ago

I can't repro the code provided passing type checking on any (mypy, python) version:

~/tmp λ cat test65.py
stringy = "foo"

f"""\
{stringy.nope()}
"""  # type: ignore

~/tmp λ python3.7 -m mypy --version
mypy 0.782
~/tmp λ python3.7 -m mypy test65.py 
test65.py:4: error: "str" has no attribute "nope"
Found 1 error in 1 file (checked 1 source file)

~/tmp λ python3.6 -m mypy --version    
mypy 0.782
~/tmp λ python3.6 -m mypy test65.py
test65.py:4: error: "str" has no attribute "nope"
Found 1 error in 1 file (checked 1 source file)

~/tmp λ python3.7 -m pip install mypy==0.770
[...]
~/tmp λ python3.7 -m mypy --version         
mypy 0.770
~/tmp λ python3.7 -m mypy test65.py         
test65.py:4: error: "str" has no attribute "nope"
Found 1 error in 1 file (checked 1 source file)

Two workarounds: you can alias the expression before your f-string or use cast(Any, stringy).nope().

If there is an issue here, it's probably going to be a wontfix, because mypy gets the location of type-ignores from stdlib's AST in 3.8.

wadetregaskis-linkedin commented 3 years ago

So you're saying multiline format strings are always going behave this way now? That kinda says that they're just fundamentally incompatible with mypy… that seems… suboptimal, to say the least. :(

hauntsaninja commented 3 years ago

First and foremost, I'm saying I can't repro your issue (that the behaviour of mypy changes here when using different Python versions).

wadetregaskis-linkedin commented 3 years ago

Sure, and I don't know why that is. But what you're saying is that for you this happens with all versions of Python. That's even worse!

I did indeed ultimately have to pull out virtually all the embedded expressions, into separate variables, so I could # type: ignore each one. It added an entire extra screen of boilerplate code, on top of being very tedious to do. Sure, not the worst thing in the world, but it's particularly surprising in a language like Python that otherwise encourages readability & brevity.

hauntsaninja commented 3 years ago

You're right, I probably shouldn't have been so hasty to close. I took a look at the code and there seems to be opportunity to lie about lineno's of JoinedStr values in fastparse.

That said, if you find that there is a repro that passes on some (mypy, python_version) combination, that would be great and increases the chance this issue has a happy ending.

wadetregaskis-linkedin commented 3 years ago

I tried figuring out what's special about my local setup, but wasn't able to - it's a bunch of LinkedIn-specific & -internal stuff, with plenty of complexity, so it's neither all that surprising that the behaviour is different, nor trivial to figure out why.

And important follow-up though re. workarounds: I didn't know no_type_check existed, until you (@hauntsaninja) mentioned it on #9483. Just to reiterate what I also said there: that's a much better workaround than the others I knew of. It's not quite as nice as what I requested here, but it's close - I just have to wrap the code in question in a dummy, nested function in order to be able to apply the decorator. Silly enough that it requires a comment documenting it, but quite functional otherwise.

gvanrossum commented 3 years ago

I do see a slight difference between 3.7-, and 3.8+. Up to 3.7, the error points at the t of stringy.nope(). But for 3.8 and later, it points to the t. Maybe that's a clue to something?

gvanrossum commented 3 years ago

(The major difference is that when mypy is run using 3.7, the parser is typed_ast, while with 3.8+ it's the stdlib ast module.)

wadetregaskis-linkedin commented 3 years ago

Just FYI, this is still broken with Python 3.9.x (w/ mypy 0.790 & 0.800, at least).

kipkoan commented 1 year ago

This is still broken with Python 3.11.2 (w/ mypy 1.0.1).

gvanrossum commented 1 year ago

Once PEP 701 (https://peps.python.org/pep-0701) is accepted you should be able to put the comment inside the interpolation.

jenstroeger commented 5 months ago

Just ran into the same issue, thanks @gvanrossum for pointing out the PEP!

Looks like Python 3.12 does indeed allow comments inside of an f-string and mypy picks them up. So, @wadetregaskis-linkedin’s example above now works:

stringy = "foo"

f"""{
stringy.nope()  # type: ignore
}"""

Note, however, the placement of the curly braces!