You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Unsafe overrides are pretty common when annotating legacy codebases. The recommended way to deal with this is to add a # type: ignore comment. This has a few major issues, though:
It's not intuitive. Users often don't realize that they should use it in cases like this.
Users frequently misunderstand # type: ignore and believe that it ignores the type annotation, even though it only ignores the error message.
Users are sometimes confused about the line on which the comment should be placed. Also, it may be unclear what to do if there is an existing comment on the line.
Some users don't like to litter their code with magic comments.
A # type: ignore can ignore unrelated errors that the user doesn't want to be ignored.
I propose to add a decorator that can be used to silence this error, and only this error. Example:
frommypy_extensionsimportunsafe_overrideclassResult: ...
classThing:
@unsafe_overridedef__eq__(self, other: object) ->Result: # Result is an unsafe return type# ... omitted
This would have these benefits:
There is less room for misunderstanding, since the decorator is explicit about what it's used for.
It's easier to use, since it doesn't have to be on a specific line.
It doesn't look as magical/messy as a magic comment.
It only ignores this specific error condition, so it won't hide unrelated errors.
It would be easy to Google documentation for the decorator when reading code. Documentation can be explicit about why and when this should be used and provide extended discussion.
See #2783 and #1237 for relevant previous discussions.
Users frequently misunderstand # type: ignore and believe that it ignores the type annotation, even though it only ignores the error message.
I think this is a more general problem. I have seen this some many times in different contexts. It is not possible to rename this, but maybe we can add a large note somewhere early in the docs explaining that # type: ignore only ignores type errors, not types?
One additional weakness of the # type: ignore workaround that isn't just cosmetic/UX-related:
When the type signatures are different, the overload resolution uses both sets of signatures for the function. An example might go something like this:
I imagine this is the way it is because we've disabled the type check, so there are implicit assumptions in the code that are no longer true. When we go to implement the unsafe_override decorator, it would be good to make shadowing behave "normally" (i.e. completely take over).
I'm less concerned about the ignore on the override itself (I agree the new code-specific stuff will make that safer as far as specificity), but the downstream type signature that seems to be a union of the two signatures, something that I haven't seen elsewhere. Just to reiterate my example with a bit more detail + up-to-date mypy output:
main.py:15:13: note: Revealed type is 'main.Expression'
main.py:16:13: note: Revealed type is 'builtins.bool'
main.py:17:13: note: Revealed type is 'main.Expression'
main.py:18:13: note: Revealed type is 'main.Expression'
main.py:18:22: error: Argument 1 to "eq" of "Foo" has incompatible type "str"; expected "int" [arg-type]
reveal_type(Foo().eq('1')) # bool (!!!)
Lines 15 and 16 should either both reveal bool (i.e. use the object.__eq__ signature), or both be Expression and emit an arg-type error like on line 18. It seems like it's treating the override as an overload, which is not what normally happens when a superclass's signature is overridden forcefully:
class A:
def foo(self, x: int) -> int:
return x
class B(A):
def foo(self, x: str) -> str: # type: ignore[override]
return x
reveal_type(A().foo(0)) # int
reveal_type(A().foo('')) # int + arg-type error
reveal_type(B().foo(0)) # str + arg-type error
reveal_type(B().foo('')) # str
It seems like there must be some special magical handling for __eq__ which is making it override incompletely.
Activity
ilevkivskyi commentedon Oct 2, 2018
I think this is a more general problem. I have seen this some many times in different contexts. It is not possible to rename this, but maybe we can add a large note somewhere early in the docs explaining that
# type: ignoreonly ignores type errors, not types?gvanrossum commentedon Oct 2, 2018
llchan commentedon Jan 25, 2019
One additional weakness of the
# type: ignoreworkaround that isn't just cosmetic/UX-related:When the type signatures are different, the overload resolution uses both sets of signatures for the function. An example might go something like this:
The expected result would be something like
I imagine this is the way it is because we've disabled the type check, so there are implicit assumptions in the code that are no longer true. When we go to implement the
unsafe_overridedecorator, it would be good to make shadowing behave "normally" (i.e. completely take over).JukkaL commentedon Jan 29, 2020
# type: ignore[override]can be used to ignore the error more safely. Perhaps it's sufficient to document this use case more prominently.llchan commentedon Feb 6, 2020
I'm less concerned about the ignore on the override itself (I agree the new code-specific stuff will make that safer as far as specificity), but the downstream type signature that seems to be a union of the two signatures, something that I haven't seen elsewhere. Just to reiterate my example with a bit more detail + up-to-date mypy output:
Lines 15 and 16 should either both reveal
bool(i.e. use theobject.__eq__signature), or both beExpressionand emit an arg-type error like on line 18. It seems like it's treating the override as an overload, which is not what normally happens when a superclass's signature is overridden forcefully:It seems like there must be some special magical handling for
__eq__which is making it override incompletely.hauntsaninja commentedon Apr 14, 2023
I think
# type: ignore[override]is a good solution for the original problem here