python / mypy

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

False positive when using `Self` as the return type with multiple base classes #17704

Open Cnoor0171 opened 4 weeks ago

Cnoor0171 commented 4 weeks ago

Bug Report

If you try to derive from multiple base classes that both define the same function returning Self, mypy incorrectly warns that the definitions are incompatible.

To Reproduce

# https://mypy-play.net/?mypy=latest&python=3.12&gist=e5d3734df862e8f3ada5c11c076cae57
# main.py
from typing import Self

class MyBase1:
    def foo(self) -> Self:
        return self

class MyBase2:
    def foo(self) -> Self:
        return self

class MyDerived(MyBase1, MyBase2):
    pass

MyBase1().foo()  # Should be Valid. Has type MyBase1
MyBase1().foo()  # Should be Valid. Has type MyBase2
MyDerived().foo()  # Should be Valid. Has type MyDerived

Expected Behavior

Shouldn't report any errors. In the context of the line class MyDerived(MyBase1, MyBase2): both MyBase1 and MyBase2 returns Self aka MyDerived

Actual Behavior

$ mypy main.py
main.py:13: error: Definition of "foo" in base class "MyBase1" is incompatible with definition in base class "MyBase2"  [misc]
Found 1 error in 1 file (checked 1 source file)

Your Environment

Cnoor0171 commented 4 weeks ago

A similar problem if the methods are classmethods

from typing import Self

class MyBase1:
    @classmethod
    def foo(cls) -> Self:
        return cls()

class MyBase2:
    @classmethod
    def foo(cls) -> Self:
        return cls()

class MyDerived(MyBase1, MyBase2):
    pass

MyBase1.foo()  # Should be Valid. Has type MyBase1
MyBase1.foo()  # Should be Valid. Has type MyBase2
MyDerived.foo()  # Should be Valid. Has type MyDerived

Interestingly, mypy is also able to derive the correct type of MyDerived.foo() or MyDerived().foo() if you do reveal_type on them.