Skip to content

Commit be883f6

Browse files
dhalenokilevkivskyi
authored andcommitted
Report an error if final class has abstract attributes (#8332)
Closes #8316.
1 parent 41b40aa commit be883f6

File tree

3 files changed

+65
-0
lines changed

3 files changed

+65
-0
lines changed

docs/source/final_attrs.rst

+14
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,17 @@ Here are some situations where using a final class may be useful:
219219
base classes and subclasses.
220220
* You want to retain the freedom to arbitrarily change the class implementation
221221
in the future, and these changes might break subclasses.
222+
223+
An abstract class that defines at least one abstract method or
224+
property and has ``@final`` decorator will generate an error from
225+
mypy, since those attributes could never be implemented.
226+
227+
.. code-block:: python
228+
229+
from abc import ABCMeta, abstractmethod
230+
from typing_extensions import final
231+
232+
@final
233+
class A(metaclass=ABCMeta): # error: Final class A has abstract attributes "f"
234+
@abstractmethod
235+
def f(self, x: int) -> None: pass

mypy/semanal_classprop.py

+4
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ def report(message: str, severity: str) -> None:
109109
report("Class {} has abstract attributes {}".format(typ.fullname, attrs), 'error')
110110
report("If it is meant to be abstract, add 'abc.ABCMeta' as an explicit metaclass",
111111
'note')
112+
if typ.is_final and abstract:
113+
attrs = ", ".join('"{}"'.format(attr) for attr in sorted(abstract))
114+
errors.report(typ.line, typ.column,
115+
"Final class {} has abstract attributes {}".format(typ.fullname, attrs))
112116

113117

114118
def check_protocol_status(info: TypeInfo, errors: Errors) -> None:

test-data/unit/check-classes.test

+47
Original file line numberDiff line numberDiff line change
@@ -6677,3 +6677,50 @@ class B:
66776677
# N: Perhaps you need "Callable[...]" or a callback protocol?
66786678

66796679
[builtins fixtures/classmethod.pyi]
6680+
6681+
[case testFinalClassWithAbstractAttributes]
6682+
from abc import abstractmethod, ABCMeta
6683+
from typing import final
6684+
6685+
@final
6686+
class A(metaclass=ABCMeta): # E: Final class __main__.A has abstract attributes "bar", "foo"
6687+
@abstractmethod
6688+
def foo(self):
6689+
pass
6690+
6691+
@property
6692+
@abstractmethod
6693+
def bar(self):
6694+
pass
6695+
6696+
[builtins fixtures/property.pyi]
6697+
6698+
[case testFinalClassWithoutABCMeta]
6699+
from abc import abstractmethod
6700+
from typing import final
6701+
6702+
@final
6703+
class A(): # E: Final class __main__.A has abstract attributes "bar", "foo"
6704+
@abstractmethod
6705+
def foo(self):
6706+
pass
6707+
6708+
@property
6709+
@abstractmethod
6710+
def bar(self):
6711+
pass
6712+
6713+
[builtins fixtures/property.pyi]
6714+
6715+
[case testFinalClassInheritedAbstractAttributes]
6716+
from abc import abstractmethod, ABCMeta
6717+
from typing import final
6718+
6719+
class A(metaclass=ABCMeta):
6720+
@abstractmethod
6721+
def foo(self):
6722+
pass
6723+
6724+
@final
6725+
class B(A): # E: Final class __main__.B has abstract attributes "foo"
6726+
pass

0 commit comments

Comments
 (0)