Ignore mypy-specific type errors

(There’s previous discussion about this in mypy’s GitHub issues: Support `mypy: ignore` inline comments to suppress mypy errors · Issue #12358 · python/mypy · GitHub)

Currently, there is no way to ignore mypy-specific type errors. It’s possible to ignore typing errors for all type checkers using # type: ignore and pyright supports ignore pyright-specific errors using # pyright: ignore:

x: int = ""   # type: ignore  # works for all type checkers
assert_type(3**4, int)  # pyright: ignore
# assert_type(2**4, Literal[16])  # no way to ignore this for mypy only

There have been two suggestions to solve this problem:

  1. Introduce a mypy: ignore directive. The main problem is that mypy uses the stdlib ast module to parse Python files, meaning that ast would need to support this somehow. @NeilGirdhar suggested to add a flag to support this to ast in this thread and gh-101494.
  2. Extend the # type: ignore syntax to allow type-checker specific ignores. Currently, # type: ignore is underspecified in the Typing Specification as mypy already extends the syntax by allowing constructs like # type: ignore[foo,bar] to ignore specific error codes. This syntax could be properly spec’ed and extended.

Personally, I like the second solution better, especially since – apart from type checker changes – it only requires a change to the Typing Spec, instead of changes to ast for a more esoteric feature.

3 Likes

I prefer the first option. Option 2 will cause backward compatibility problems and significant churn for some pyright users. Mypy really should have its own way of disabling mypy-specific errors.

I’m not convinced that mypy needs a change to the ast to support a mypy-specific directive. It has been suggested in other threads that it could use a combination of the ast (which contains file locations in the ast nodes) and the original file text to retrieve end-of-line comments. Has anyone tried to implement this? I think this should at least be prototyped before we consider extending the existing hack in the ast module or contemplate changing the typing spec to accommodate mypy’s current implementation limitations.

Another option to consider is for mypy to incorporate its own copy of the peg parser and ast. This is a bigger change, but it would allow mypy to support newer language features when running on older versions of Python. Mypy’s current dependency on the ast module prevents us from leveraging newer language features (like PEP 695) in stubs.

2 Likes

I’m interested in how this would cause backward compatibility problems? I’m not even sure that pyright would need to change.

This is indeed an ongoing concern with mypy. If we’d change the ast module, mypy would only be able to leverage this feature when dropping support for Python 3.13, many years in the future. mypy shipping its own copy of the ast module (similar how it used to used typed_ast in the past) would be helpful, but the last time I looked at it, the ast module is deeply connected to the rest of the Python interpreter and the build system, so “freeing” it (and keeping it in sync) would be a major undertaking.

Pyright follows the current typing spec. If it sees a # type: ignore on a line (regardless of what comes after the # type: ignore) it suppresses all type checking diagnostics on that line. If the typing spec were to change and indicate that type checkers now need to conditionally suppress subsets of diagnostics on a line if a # type: ignore is followed by square brackets, then pyright’s behavior would need to change accordingly. If the square brackets were to contain only items that pyright doesn’t know about, it would presumably not suppress any diagnostics on that line, which would be a change in behavior from today. This would further trigger reportUnnecessaryTypeIgnoreComment diagnostics.

I’ll also note that the type checker-specific form # pyright: ignore[x, y, z] allows pyright to tell developers they have misspelled one of the rules listed in square brackets, whereas with # type: ignore[x, y, z] it would need to simply ignore any potential misspellings because they could be rules that apply to other type checkers — unless the new syntax allowed for some namespace mechanism like # type: ignore[mypy:assignment, pyright:reportAssignmentType].

That’s not how I interpret the current wording (which is why I said the section was “underspecified” above):

The special comment # type: ignore is used to silence type checker errors.

I would argue that the behavior of # type: ignore[...] is unspecified. That said, pyright’s interpretation is sensible. Maybe as a first step we could complete the specification to match real world usage. I’d need to look at the behavior of current type checkers to make a concrete proposal, but something along the lines that type checkers can narrow the ignored errors using type checker-specific error codes using the syntax currently used by mypy. Unknown errors codes should turn this into an unrestricted # type: ignore. Type checkers may warn about that.

And then in a second step build on that by extending the syntax, for example by using the prefix notation you suggested.

I think it would be better for mypy to add support for its own suppression comment. Other type checkers already do this. I don’t want to codify a mechanism that is less usable just because mypy has a current internal implementation limitation. As I mentioned above, I suspect this limitation is artificial and could be worked around.

That’s a good suggestion if we go with option 2. It would retain current pyright behaviors and therefore eliminate one of my objections to the proposal. I still prefer option 1.

Probably not. There would be no way to suppress such a warning.

2 Likes

PEP 8 says that a line should be no longer than 79 characters.

An average Mypy error code is 13 characters long:

This is much shorter than that of, say, Pyright (~24, 18 if report can somehow be excluded), but it will do for an approximation.

>>> import mypy.errorcodes as m
>>> l = [ec.code for ec in vars(m).values() if isinstance(ec, m.ErrorCode)]
>>> sum(len(c) for c in l) / len(l)
12.55072463768116

Thus, a properly formatted ignore comment takes 18 + 13 = 31 characters.

  # type: ignore[abcdefghijklm]

OOP is common, so let’s say I’m inside a method, at the second indentation level; that takes 8 more.

Since I’m inside a method, chances are that I’m working with an attribute of self. I aim for readability, so the length of that attribute’s name is about 10. Accessing an attribute by itself generally doesn’t do anything useful, so it must be a part of a method call (()), an assignment (= and 2 surrounding spaces) or a function call (() + 10).

If this hypothetical syntax allows differentiating type checkers, by the principle of least astonishment it must use the type checker’s name, which costs about 4 (mypy, pyre) to 7 (pyright, pycharm) characters, as well as a separator character to separate that name and the corresponding rule (e.g. mypy:override).

All of these leave me with at best (79 - 39 - 15) - 2 - 5 = 18 characters and at worst 25 - 12 - 8 = 5 characters. Remember, I’m still at the second indent level. How would I be able to respect the limit while also making use of the new syntax if I needed to ignore a second rule by another type checker?

Option 1 is the way to go.

1 Like