python / mypy

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

False-negative `explicit-override` for `__init__` #16081

Closed bersbersbers closed 1 year ago

bersbersbers commented 1 year ago

Bug Report

To Reproduce

from typing_extensions import override

class A:
    @override  # false-negative "marked as an override"
    def __init__(self, _foo: int) -> None: ...

    @override  # true-positive "marked as an override"
    def bar(self) -> None: ...

https://gist.github.com/mypy-play/247d7a5dc46ce24298509f6f9181edc6

Expected Behavior

main.py:5: error: Method "__init__" is marked as an override, but no base method was found with this name **or signature** [misc]
main.py:8: error: Method "bar" is marked as an override, but no base method was found with this name  [misc]

Actual Behavior

main.py:8: error: Method "bar" is marked as an override, but no base method was found with this name  [misc]

Your Environment

cdce8p commented 1 year ago

Technically the @override on __init__ is correct. It's just object.__init__ which is overridden.

bersbersbers commented 1 year ago

Technically the @override on __init__ is correct. It's just object.__init__ which is overridden.

But object.__init__ does not accept an int, does it? So maybe this is not a false-negative explicit-override, but a false-negative override, which is correctly emitted in this example:

from typing_extensions import override

class Base:
    def bar(self) -> None: ...

class Class(Base):
    @override  # true-positive "Signature incompatible"
    def bar(self, _foo: int) -> None: ...
cdce8p commented 1 year ago

But object.__init__ does not accept an int, does it? So maybe this is not a false-negative explicit-override, but a false-negative override

By that logic would all custom __init__ methods be an invalid override if they add new parameters?

My general advice would be to not add @override on __init__ or __new__.

AlexWaygood commented 1 year ago

So maybe this is not a false-negative explicit-override, but a false-negative override, which is correctly emitted in this example:

No, mypy is correct in not applying the Liskov Substitution Principle to constructor or initialiser methods. See https://softwareengineering.stackexchange.com/questions/431549/the-liskov-substitution-principle-and-python for detailed answers as to why.

As @cdce8p says, it doesn't look like there's a bug here :)

bersbersbers commented 1 year ago

Thanks for the explanation. Maybe I have a wrong understanding of @override that I need to wrap my head around.

I had expected that, for a given function, either I necessarily need to add @override or I necessarily need to omit it. I had not expected that there may be cases where both having @override and not having @override are valid. Looks very similar to the case in https://github.com/microsoft/pyright/issues/4788#issuecomment-1472237480, I guess.