crates/ty_python_semantic/resources/mdtest/call/abstract_method.md
Calling an abstract @classmethod with a trivial body directly on the class is unsound.
from abc import ABC, abstractmethod
class Foo(ABC):
@classmethod
@abstractmethod
def method(cls) -> int: ...
# snapshot: call-abstract-method
Foo.method()
error[call-abstract-method]: Cannot call `method` on class object
--> src/mdtest_snippet.py:4:5
|
4 | / @classmethod
5 | | @abstractmethod
6 | | def method(cls) -> int: ...
| |_______________________________- Method `method` defined here
7 |
8 | # snapshot: call-abstract-method
9 | Foo.method()
| ^^^^^^^^^^^^ `method` is an abstract classmethod with a trivial body
|
Calling an abstract @staticmethod with a trivial body directly on the class is unsound.
from abc import ABC, abstractmethod
class Foo(ABC):
@staticmethod
@abstractmethod
def method() -> int: ...
# error: [call-abstract-method] "Cannot call `method` on class object"
Foo.method()
type[X] is allowedWhen accessed via type[X], the runtime type could be a concrete subclass.
from abc import ABC, abstractmethod
class Foo(ABC):
@classmethod
@abstractmethod
def method(cls) -> int: ...
def f(x: type[Foo]):
x.method() # fine
type[X] is allowedfrom abc import ABC, abstractmethod
class Foo(ABC):
@staticmethod
@abstractmethod
def method() -> int: ...
def f(x: type[Foo]):
x.method() # fine
An abstract method with a non-trivial body has a default implementation that can be called.
from abc import ABC, abstractmethod
class Foo(ABC):
@classmethod
@abstractmethod
def method(cls) -> int:
return 42
Foo.method() # fine
pass bodyfrom abc import ABC, abstractmethod
class Foo(ABC):
@classmethod
@abstractmethod
def method(cls) -> int:
pass
# error: [call-abstract-method] "Cannot call `method` on class object"
Foo.method()
raise NotImplementedError bodyfrom abc import ABC, abstractmethod
class Foo(ABC):
@classmethod
@abstractmethod
def method(cls) -> int:
raise NotImplementedError
# error: [call-abstract-method] "Cannot call `method` on class object"
Foo.method()
from abc import ABC, abstractmethod
class Base(ABC):
@classmethod
@abstractmethod
def method(cls) -> int: ...
class Derived(Base):
@classmethod
def method(cls) -> int:
return 42
Derived.method() # fine
If a subclass is still abstract (doesn't override the method), calling the inherited abstract classmethod on it is also unsound.
from abc import ABC, abstractmethod
class Base(ABC):
@classmethod
@abstractmethod
def method(cls) -> int: ...
class StillAbstract(Base):
pass
# error: [call-abstract-method] "Cannot call `method` on class object"
StillAbstract.method()
The @abstractmethod and @classmethod/@staticmethod decorators can appear in either order.
from abc import ABC, abstractmethod
class Foo(ABC):
@abstractmethod
@classmethod
def method(cls) -> int: ...
# error: [call-abstract-method] "Cannot call `method` on class object"
Foo.method()
A method whose body is only a docstring is also a trivial body.
from abc import ABC, abstractmethod
class Foo(ABC):
@classmethod
@abstractmethod
def method(cls) -> int:
"""This method should be overridden."""
...
# error: [call-abstract-method] "Cannot call `method` on class object"
Foo.method()
A classmethod on a Protocol class with a trivial body is implicitly abstract, even without
@abstractmethod.
from typing import Protocol
class P(Protocol):
@classmethod
def method(cls) -> int: ...
# error: [call-abstract-method] "Cannot call `method` on class object"
P.method()
from typing import Protocol
class P(Protocol):
@staticmethod
def method() -> int: ...
# error: [call-abstract-method] "Cannot call `method` on class object"
P.method()
Methods defined in stub files are never considered to have trivial bodies, since stubs use ... as
a placeholder regardless of the runtime implementation.
foo.pyi:
from abc import ABC, abstractmethod
class Foo(ABC):
@classmethod
@abstractmethod
def classmethod(cls) -> int: ...
@staticmethod
@abstractmethod
def staticmethod() -> int: ...
from foo import Foo
# These should not trigger `call-abstract-method` since the methods are defined in a stub.
Foo.classmethod()
Foo.staticmethod()