ty icon indicating copy to clipboard operation
ty copied to clipboard

Emit diagnostic on unsound `super()` call to abstract method with trivial body

Open AlexWaygood opened this issue 1 week ago • 2 comments

Mypy, pyright and pyrefly all detect the unsoundness here. We should too:

from abc import abstractmethod

class F:
    @abstractmethod
    def method(self) -> int: ...

class G(F):
    def method(self) -> int:
        # mypy: error: Call to abstract method "method" of "F" with trivial body via super() is unsafe  [safe-super]
        return super().method()

This is similar to, but distinct from, https://github.com/astral-sh/ty/issues/1877.

We should ensure that abstract properties are also covered, e.g.

from abc import abstractmethod

class F:
    @property
    @abstractmethod
    def prop(self) -> int: ...

class G(F):
    @property
    def prop(self) -> int:
        # mypy: error: Call to abstract method "method" of "F" with trivial body via super() is unsafe  [safe-super]
        return super().prop

AlexWaygood avatar Dec 16 '25 12:12 AlexWaygood

Note that the "with trivial body" part of this is important. For example, this is fine, because the abstract method has a default implementation:

from abc import abstractmethod

class F:
    @abstractmethod
    def method(self) -> int:
        return 42

class G(F):
    def method(self) -> int:
        return super().method()

As a result of the above being fine, however, this means that we must also reject super() calls to abstract methods even if the super() call occurs in the body of an overriding method that is also abstract. To see why, consider this example below:

from abc import abstractmethod

class F:
    @abstractmethod
    def method(self) -> int: ...

class G(F):
    @abstractmethod
    def method(self) -> int:
        return super().method()

class H(G):
    def method(self) -> int:
        return super().method()

H.method() says it will return int, but it actually returns None. But H.method doesn't break the rule outlined above: it calls super() on G.method, which is an abstract method that has a default implementation. Therefore the only way to prevent this unsoundness is to forbid the super() call in G.method, despite the fact that G.method is also an abstract method.

AlexWaygood avatar Dec 16 '25 14:12 AlexWaygood

Mypy and pyrefly also emit a diagnostic on G.method in this snippet, where F.method is not explicitly abstract, but is a protocol method with a trivial body. The same soundness issues occur with this, so it makes sense that we should also emit a diagnostic here:

from typing import Protocol

class F(Protocol):
    def method(self) -> int: ...

class G(F):
    def method(self) -> int:
        return super().method()

Pyright does not emit a diagnostic on this variation.

AlexWaygood avatar Dec 16 '25 16:12 AlexWaygood