Repository: python/mypy
Pull request: Report an error if final class has abstract attributes
Closes #8316.

Problem statement:
Feature request: Disallow final classes from having abstract methods or properties
Currently, mypy checks whether a class is abstract when it's instantiated. For classes with the `@final` decorator, it would make sense for there to be a typing error if they define any abstract methods or properties, since those could never be implemented.

It would also make sense for classes that explicitly have the `ABCMeta` metaclass and also have the `@final` decorator to be a typing error if the class contains any instance methods or properties, since there could never be an instance created to call them with.

Note that `abstract final` classes are an idiom that is sometimes used in Java to define a class that is just a grouping of static methods, but I believe that's mostly a result of Java not having modules and top-level functions. In Python, one could organize top-level functions and group them with a module. This could be an argument to disallow a class to have both `@final` and `ABCMeta` all-together.

Hints from issue discussion:
I like this idea. It is also easy to implement, one would just need to add a check at the end of `calculate_class_abstract_status()` giving an error if the class is final but the lost of abstract attributes is non-empty.

Commit message: Update docs to mention final class with abstract attributes

Patch to review:
diff --git a/docs/source/class_basics.rst b/docs/source/class_basics.rst
index 3a1f731fa8dd..c90b1250793d 100644
--- a/docs/source/class_basics.rst
+++ b/docs/source/class_basics.rst
@@ -321,3 +321,17 @@ class, including an abstract method defined in an abstract base class.
 
 You can implement an abstract property using either a normal
 property or an instance variable.
+
+An abstract class that defines at least one abstract method or
+property and has `@final` decorator will generate an error from
+mypy, since those attributes could never be implemented.
+
+.. code-block:: python
+
+    from abc import ABCMeta, abstractmethod
+    from typing import final
+
+    @final
+    class A(metaclass=ABCMeta):  # Error: Final class A has abstract attributes "f"
+        @abstractmethod
+        def f(self, x: int) -> None: pass
diff --git a/mypy/semanal_classprop.py b/mypy/semanal_classprop.py
index 7052a1197d04..8dc518662445 100644
--- a/mypy/semanal_classprop.py
+++ b/mypy/semanal_classprop.py
@@ -109,6 +109,10 @@ def report(message: str, severity: str) -> None:
             report("Class {} has abstract attributes {}".format(typ.fullname, attrs), 'error')
             report("If it is meant to be abstract, add 'abc.ABCMeta' as an explicit metaclass",
                    'note')
+    if typ.is_final and abstract:
+        attrs = ", ".join('"{}"'.format(attr) for attr in sorted(abstract))
+        errors.report(typ.line, typ.column,
+                      "Final class {} has abstract attributes {}".format(typ.fullname, attrs))
 
 
 def check_protocol_status(info: TypeInfo, errors: Errors) -> None:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
index 290926c80466..167e1c60e371 100644
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -6677,3 +6677,20 @@ class B:
                                         # N: Perhaps you need "Callable[...]" or a callback protocol?
 
 [builtins fixtures/classmethod.pyi]
+
+[case testFinalClassWithAbstractAttributes]
+from abc import abstractmethod, ABCMeta
+from typing import final
+
+@final
+class A(metaclass=ABCMeta):  # E: Final class __main__.A has abstract attributes "bar", "foo"
+    @abstractmethod
+    def foo(self):
+        pass
+
+    @property
+    @abstractmethod
+    def bar(self):
+        pass
+
+[builtins fixtures/property.pyi]


Code review comments:
diff_hunk: @@ -321,3 +321,17 @@ class, including an abstract method defined in an abstract base class.
 
 You can implement an abstract property using either a normal
 property or an instance variable.
+
+An abstract class that defines at least one abstract method or
+property and has `@final` decorator will generate an error from
+mypy, since those attributes could never be implemented.
+
+.. code-block:: python
+
+    from abc import ABCMeta, abstractmethod
+    from typing import final
+
+    @final
+    class A(metaclass=ABCMeta):  # Error: Final class A has abstract attributes "f"
path: docs/source/class_basics.rst
text: @user1:
```suggestion
    class A(metaclass=ABCMeta):  # error: Final class A has abstract attributes "f"
```