Allow type: ignore Mypy style error codes in conformance tests

While trying to pass the conformance tests for Zuban, I have encountered a few fundamental issues, this is #1 of 4.

Zuban - like Mypy (and with the same error codes as Mypy) - uses # type ignore[error-code] to skip specific error codes. I understand that this is a bit problematic, because the error codes differ between type checkers. The conformance tests kind of reflect this:

# The following type violation should be suppressed.
y: int = ""  # type: ignore - additional stuff      
                                                    
# The following type violation should be suppressed.
z: int = ""  # type: ignore[additional_stuff]       

The spec mentions type ignore comments like this and does not elaborate more:

In some cases, linting tools or other comments may be needed on the same line as a type comment. In these cases, the type comment should be before other comments and linting markers:
# type: ignore # <comment or other marker>

I propose to change this and to allow an error at least in the second line statement for the following reasons:

  • It’s unlikely that Mypy or Zuban ever switches away from the # type: ignore[...] style (it might support an additional # mypy-ignore[...] or something similar in addition though).
  • The error codes do not hurt interoperability with other type checkers, the other type checkers can simply ignore the specified error code, which they now have to do anyways, because Mypy exists.
  • The spec (above) does not mention how text after type ignore should behave. It only specifies how additional comments should behave.
  • I don’t think there’s a realistic chance for other type checkers to use the Mypy-like error codes. Mypy is to prevalent to conflict with it and it just makes more sense for type checkers to adapt their own error code system.

I would personally also propose to allow an error in the first statement, since Mypy at the y line simply adds an error that - additional stuff is invalid, because it’s not a comment. This is in my opinion closer to the Spec than what the conformance tests (or Pyright) says.

I understand that the situation is not optimal, Mypy would probably do this in a different way if it had the chance. Zuban cannot really ditch Mypy style type ignore comments. I can of course add a flag that enables spec conforming behavior, but people will probably not use that.

If people agree with this proposal, I will add a PR to the typing Repository and change the conformance tests.

As you point out, it’s unlikely that we will standardize error codes across type checkers. With that in mind, I think it’s valuable for the type system to include a type-checker-neutral way to suppress diagnostics on a line. It already provides this: # type: ignore.

I think it’s also useful for a type checker to provide a type-checker-specific way to suppress specific diagnostics on a line. Since this is a type-checker-specific behavior, there is no need to standardize it in the typing spec. Pyright provides # pyright: ignore[x, y, z]. Pyrefly and ty have followed suit here and provided their own # pyrefly: ignore and # ty: ignore comments, respectively.

As I understand it, the reason mypy has not (yet) supported a # mypy: ignore is because it currently depends on the Python parser, which has special accommodations for # type: ignore comments. Am I correct in assuming that Zuban does not have such a limitation? There are other good reasons for mypy to move away from the Python parser (performance, version lock-in, etc.), and its maintainers are considering such a move.

I’d prefer not to codify a suboptimal behavior in the spec to accommodate a legacy mypy behavior, especially if there’s a reasonable chance that mypy is on a path to modifying this behavior.

I think if a solution for all checkers would be made, type: ignore[<code>] is the best option. Each checker could parse them on their own, if wanted. Error codes would need to be standardized, but I think that this wouldn’t be too much of an issue.

Sadly backwards compatibility is lost, and for the next few versions, not old style ignores, and the new universal one would be needed, but that also shouldn’t be too hard to migrate, mainly just a bunch of search and replaces.

Therefore I’m in favor of finding a uniform way of communicating with type-checkers.

As I understand it, the reason mypy has not (yet) supported a # mypy: ignore is because it currently depends on the Python parser, which has special accommodations for # type: ignore comments. Am I correct in assuming that Zuban does not have such a limitation? There are other good reasons for mypy to move away from the Python parser (performance, version lock-in, etc.), and its maintainers are considering such a move.

The missing # mypy: ignore might be due to a parser limitation, but even if Mypy eventually gets parser support for this, Mypy will not get rid of # type: ignore[<code>], because it’s just an enormous change to the Mypy ecosystem. Usage of these error codes is very common and I doubt it would make sense for Mypy to get rid of this behavior.

As long as Mypy uses this way to skip particular error codes, I don’t want to ditch this and use the conformance tests way, because that would introduce more unsafety, since it would just skip all error codes and not just the particular ones. I don’t want to give the users less safety and conforming with the conformance tests will make this happen. Note that adding a flag like --ignore-type-ignore-error-codes or --make-conformance-tests-pass is way less work for me than discussing this issue here. But I don’t want users to actually use that flag.

I’d prefer not to codify a suboptimal behavior in the spec to accommodate a legacy mypy behavior, especially if there’s a reasonable chance that mypy is on a path to modifying this behavior.

But it is not codified in the spec, quite the contrary. The spec says nothing about additional characters after type ignore other than comments. I think it is very reasonable and in my opinion correct according to the spec for Mypy to add an issue for having an invalid type comment like # type: ignore blabla. The conformance tests are the problem here, not the spec.

2 Likes

Treating # type: ignore - additional stuff as a valid # type: ignore makes more sense to me than erroring on it. The intent seems pretty clear to write an error suppression plus some comment. I agree that the spec doesn’t currently specify what to do here, but if we were going to update it, I’d rather codify what’s currently in the conformance test (which pyright, pyrefly, and ty already implement) than a suboptimal behavior driven by a limitation in mypy.

For # type: ignore[additional_stuff], I can see the argument for allowing an error about additional_stuff being an invalid code, but it seems tricky to update that line to allow both mypy’s and pyright’s behavior, since they’re so different. (Mypy also reports an assignment error, since that error code isn’t suppressed.) Maybe we could change it to:

z: int = ""  # type: ignore[assignment]

so that the expected behavior in all type checkers is that there’s no error?

That would definitely work. I’m happy to update the tests if that’s a fine change.

Treating # type: ignore - additional stuff as a valid # type: ignore makes more sense to me than erroring on it.

I generally agree, it’s just that Mypy generates two errors there: Assignment error & raising an error about invalid type errors. I think Mypy’s behavior is fine there, but I don’t mind at all if we decide to make a # type: ignore - additional stuff a valid type: ignore. I’m not against that at all, I just think Mypy’s behavior is fine as well.

I’d like us to make the spec more complete in ways which are compatible with the mypy behavior, where possible. I think this is possible, without harming other checkers, by leaning into mypy’s [code] notation and making the spec more precise regarding comment syntax.

First, regarding the codes themselves, the [code] notation could be expanded to support namespaces, similar to warning filters.

That is, make it part of the spec, something like

x: int = """  # type: ignore[mypy:assignment, zuban: assignment]

Codes without namespaces are interpreted by all type checkers, but namespaced ones are meant for the checker whose PyPI package name matches the namespace portion.

Second, to handle the text after the ignore more uniformly, specify that any text after a bare ignore or the closing bracket of an ignore[<code list>] is not part of the type-ignore and that checkers

  • MUST ignore it if it’s a secondary comment with #
  • MAY error or warn if not

The cited conformance test would not need to change because these rules make the spec align with the test.

1 Like

Currently, optype supports mypy, pyright, basedpyright, pyrefly and ty. There are situations where ignoring typeing errors is inevitable. For example:

@override
def __eq__(self, rhs: _T_object_contra, /) -> _T_bool_co: ...  # type: ignore[override]  # pyright:ignore[reportIncompatibleMethodOverride]  # pyrefly: ignore[bad-override]  # ty: ignore[invalid-method-override]

source

So yea, I’d also would really appreciate a standard for ignoring typing errors. I’m not asking to standardize every kind of error, just the potentially inevitable ones, such as this override issue.

While I don’t have a strong opionion, what about # type: ignored though? Or # type: ignoreClass, which might be an actual class and therefore a valueable type? I feel like it’s a bit unlucky that an actual ignore type might exist as well, but that might not be as relevant as the ones I just brought up. I’m completely fine with ignoring code with # type: ignore - , but it feels like a genuine issue to just ignore the line if the comment has a substring of type: ignore. I’m not sure how to salvage that though. What do you think?

We could require whitespace after the ignore. So # type: ignored would not be a valid ignore, but # type: ignore whatever would be.

(I checked what pyrefly does, and we require either whitespace or a “word boundary” (https://github.com/facebook/pyrefly/blob/4d38ea04b79bb578760daf3c3e3a620f2b8ce124/crates/pyrefly_python/src/ignore.rs#L152), which does let us accept some things like # type: ignore, is this an acceptable ignore?, but I feel like that might be a little too complicated. From some quick testing in the playground, I think pyright uses whitespace.)

1 Like