Repository: python/mypy

Issue:
`Enum` types cannot be inherited
**Feature**

## Subtyping with `Enum` exact values

This core currently passes `mypy`:

```python
from enum import Enum

class A(Enum):
   x = 1

class B(A):
    ...
```

But, in runtime this code fails with:

```
Traceback (most recent call last):
  File "ex.py", line 6, in <module>
    class B(A):
  File "/Users/sobolev/.pyenv/versions/3.8.9/lib/python3.8/enum.py", line 146, in __prepare__
    metacls._check_for_existing_members(cls, bases)
  File "/Users/sobolev/.pyenv/versions/3.8.9/lib/python3.8/enum.py", line 527, in _check_for_existing_members
    raise TypeError(
TypeError: B: cannot extend enumeration 'A'
```

This is also the case for `Flag`, `IntEnum`, and `IntFlag`.

## Subtyping without exact values

However, subtyping without `Enum` values is fine:

```python
from enum import Enum

class A(Enum):
   ...

class B(A):
    ...
```

This is also the case for `Flag`, `IntEnum`, and `IntFlag`.

So, the rule is: types inherited from `Enum` with at least one enumeration value should be marked as implicitly `final` type.

Related #10852 
Related #5599 


Discussion:
I would love to work on it after #10852 

Patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -76,7 +76,8 @@
     get_nongen_builtins, get_member_expr_fullname, REVEAL_TYPE,
     REVEAL_LOCALS, is_final_node, TypedDictExpr, type_aliases_source_versions,
     EnumCallExpr, RUNTIME_PROTOCOL_DECOS, FakeExpression, Statement, AssignmentExpr,
-    ParamSpecExpr, EllipsisExpr
+    ParamSpecExpr, EllipsisExpr,
+    FuncBase, implicit_module_attrs,
 )
 from mypy.tvar_scope import TypeVarLikeScope
 from mypy.typevars import fill_typevars
@@ -95,7 +96,6 @@
 )
 from mypy.typeops import function_type
 from mypy.type_visitor import TypeQuery
-from mypy.nodes import implicit_module_attrs
 from mypy.typeanal import (
     TypeAnalyser, analyze_type_alias, no_subscript_builtin_alias,
     TypeVarLikeQuery, TypeVarLikeList, remove_dups, has_any_from_unimported_type,
@@ -1096,8 +1096,8 @@ def analyze_class(self, defn: ClassDef) -> None:
         self.update_metaclass(defn)
 
         bases = defn.base_type_exprs
-        bases, tvar_defs, is_protocol = self.clean_up_bases_and_infer_type_variables(defn, bases,
-                                                                                     context=defn)
+        bases, tvar_defs, is_protocol = self.clean_up_bases_and_infer_type_variables(
+            defn, bases, context=defn)
 
         for tvd in tvar_defs:
             if any(has_placeholder(t) for t in [tvd.upper_bound] + tvd.values):
@@ -1521,6 +1521,19 @@ def configure_base_classes(self,
             elif isinstance(base, Instance):
                 if base.type.is_newtype:
                     self.fail('Cannot subclass "NewType"', defn)
+                if (
+                    base.type.is_enum
+                    and base.type.fullname not in (
+                        'enum.Enum', 'enum.IntEnum', 'enum.Flag', 'enum.IntFlag')
+                    and base.type.names
+                    and any(not isinstance(n.node, (FuncBase, Decorator))
+                            for n in base.type.names.values())
+                ):
+                    # This means that are trying to subclass a non-default
+                    # Enum class, with defined members. This is not possible.
+                    # In runtime, it will raise. We need to mark this type as final.
+                    # However, methods can be defined on a type: only values can't.
+                    base.type.is_final = True
                 base_types.append(base)
             elif isinstance(base, AnyType):
                 if self.options.disallow_subclassing_any:


Test patch:
diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test
--- a/test-data/unit/check-enum.test
+++ b/test-data/unit/check-enum.test
@@ -1391,3 +1391,256 @@ class Foo(Enum):
         x = 3
 x = 4
 [builtins fixtures/bool.pyi]
+
+[case testEnumImplicitlyFinalForSubclassing]
+from enum import Enum, IntEnum, Flag, IntFlag
+
+class NonEmptyEnum(Enum):
+    x = 1
+class NonEmptyIntEnum(IntEnum):
+    x = 1
+class NonEmptyFlag(Flag):
+    x = 1
+class NonEmptyIntFlag(IntFlag):
+    x = 1
+
+class ErrorEnumWithValue(NonEmptyEnum):  # E: Cannot inherit from final class "NonEmptyEnum"
+    x = 1
+class ErrorIntEnumWithValue(NonEmptyIntEnum):  # E: Cannot inherit from final class "NonEmptyIntEnum"
+    x = 1
+class ErrorFlagWithValue(NonEmptyFlag):  # E: Cannot inherit from final class "NonEmptyFlag"
+    x = 1
+class ErrorIntFlagWithValue(NonEmptyIntFlag):  # E: Cannot inherit from final class "NonEmptyIntFlag"
+    x = 1
+
+class ErrorEnumWithoutValue(NonEmptyEnum):  # E: Cannot inherit from final class "NonEmptyEnum"
+    pass
+class ErrorIntEnumWithoutValue(NonEmptyIntEnum):  # E: Cannot inherit from final class "NonEmptyIntEnum"
+    pass
+class ErrorFlagWithoutValue(NonEmptyFlag):  # E: Cannot inherit from final class "NonEmptyFlag"
+    pass
+class ErrorIntFlagWithoutValue(NonEmptyIntFlag):  # E: Cannot inherit from final class "NonEmptyIntFlag"
+    pass
+[builtins fixtures/bool.pyi]
+
+[case testSubclassingNonFinalEnums]
+from enum import Enum, IntEnum, Flag, IntFlag, EnumMeta
+
+def decorator(func):
+    return func
+
+class EmptyEnum(Enum):
+    pass
+class EmptyIntEnum(IntEnum):
+    pass
+class EmptyFlag(Flag):
+    pass
+class EmptyIntFlag(IntFlag):
+    pass
+class EmptyEnumMeta(EnumMeta):
+    pass
+
+class NonEmptyEnumSub(EmptyEnum):
+    x = 1
+class NonEmptyIntEnumSub(EmptyIntEnum):
+    x = 1
+class NonEmptyFlagSub(EmptyFlag):
+    x = 1
+class NonEmptyIntFlagSub(EmptyIntFlag):
+    x = 1
+class NonEmptyEnumMetaSub(EmptyEnumMeta):
+    x = 1
+
+class EmptyEnumSub(EmptyEnum):
+    def method(self) -> None: pass
+    @decorator
+    def other(self) -> None: pass
+class EmptyIntEnumSub(EmptyIntEnum):
+    def method(self) -> None: pass
+class EmptyFlagSub(EmptyFlag):
+    def method(self) -> None: pass
+class EmptyIntFlagSub(EmptyIntFlag):
+    def method(self) -> None: pass
+class EmptyEnumMetaSub(EmptyEnumMeta):
+    def method(self) -> None: pass
+
+class NestedEmptyEnumSub(EmptyEnumSub):
+    x = 1
+class NestedEmptyIntEnumSub(EmptyIntEnumSub):
+    x = 1
+class NestedEmptyFlagSub(EmptyFlagSub):
+    x = 1
+class NestedEmptyIntFlagSub(EmptyIntFlagSub):
+    x = 1
+class NestedEmptyEnumMetaSub(EmptyEnumMetaSub):
+    x = 1
+[builtins fixtures/bool.pyi]
+
+[case testEnumExplicitlyAndImplicitlyFinal]
+from typing import final
+from enum import Enum, IntEnum, Flag, IntFlag, EnumMeta
+
+@final
+class EmptyEnum(Enum):
+    pass
+@final
+class EmptyIntEnum(IntEnum):
+    pass
+@final
+class EmptyFlag(Flag):
+    pass
+@final
+class EmptyIntFlag(IntFlag):
+    pass
+@final
+class EmptyEnumMeta(EnumMeta):
+    pass
+
+class EmptyEnumSub(EmptyEnum):  # E: Cannot inherit from final class "EmptyEnum"
+    pass
+class EmptyIntEnumSub(EmptyIntEnum):  # E: Cannot inherit from final class "EmptyIntEnum"
+    pass
+class EmptyFlagSub(EmptyFlag):  # E: Cannot inherit from final class "EmptyFlag"
+    pass
+class EmptyIntFlagSub(EmptyIntFlag):  # E: Cannot inherit from final class "EmptyIntFlag"
+    pass
+class EmptyEnumMetaSub(EmptyEnumMeta):  # E: Cannot inherit from final class "EmptyEnumMeta"
+    pass
+
+@final
+class NonEmptyEnum(Enum):
+    x = 1
+@final
+class NonEmptyIntEnum(IntEnum):
+    x = 1
+@final
+class NonEmptyFlag(Flag):
+    x = 1
+@final
+class NonEmptyIntFlag(IntFlag):
+    x = 1
+@final
+class NonEmptyEnumMeta(EnumMeta):
+    x = 1
+
+class ErrorEnumWithoutValue(NonEmptyEnum):  # E: Cannot inherit from final class "NonEmptyEnum"
+    pass
+class ErrorIntEnumWithoutValue(NonEmptyIntEnum):  # E: Cannot inherit from final class "NonEmptyIntEnum"
+    pass
+class ErrorFlagWithoutValue(NonEmptyFlag):  # E: Cannot inherit from final class "NonEmptyFlag"
+    pass
+class ErrorIntFlagWithoutValue(NonEmptyIntFlag):  # E: Cannot inherit from final class "NonEmptyIntFlag"
+    pass
+class ErrorEnumMetaWithoutValue(NonEmptyEnumMeta):  # E: Cannot inherit from final class "NonEmptyEnumMeta"
+    pass
+[builtins fixtures/bool.pyi]
+
+[case testEnumFinalSubtypingEnumMetaSpecialCase]
+from enum import EnumMeta
+# `EnumMeta` types are not `Enum`s
+class SubMeta(EnumMeta):
+    x = 1
+class SubSubMeta(SubMeta):
+    x = 2
+[builtins fixtures/bool.pyi]
+
+[case testEnumFinalSubtypingOverloadedSpecialCase]
+from typing import overload
+from enum import Enum, IntEnum, Flag, IntFlag, EnumMeta
+
+class EmptyEnum(Enum):
+    @overload
+    def method(self, arg: int) -> int:
+        pass
+    @overload
+    def method(self, arg: str) -> str:
+        pass
+    def method(self, arg):
+        pass
+class EmptyIntEnum(IntEnum):
+    @overload
+    def method(self, arg: int) -> int:
+        pass
+    @overload
+    def method(self, arg: str) -> str:
+        pass
+    def method(self, arg):
+        pass
+class EmptyFlag(Flag):
+    @overload
+    def method(self, arg: int) -> int:
+        pass
+    @overload
+    def method(self, arg: str) -> str:
+        pass
+    def method(self, arg):
+        pass
+class EmptyIntFlag(IntFlag):
+    @overload
+    def method(self, arg: int) -> int:
+        pass
+    @overload
+    def method(self, arg: str) -> str:
+        pass
+    def method(self, arg):
+        pass
+class EmptyEnumMeta(EnumMeta):
+    @overload
+    def method(self, arg: int) -> int:
+        pass
+    @overload
+    def method(self, arg: str) -> str:
+        pass
+    def method(self, arg):
+        pass
+
+class NonEmptyEnumSub(EmptyEnum):
+    x = 1
+class NonEmptyIntEnumSub(EmptyIntEnum):
+    x = 1
+class NonEmptyFlagSub(EmptyFlag):
+    x = 1
+class NonEmptyIntFlagSub(EmptyIntFlag):
+    x = 1
+class NonEmptyEnumMetaSub(EmptyEnumMeta):
+    x = 1
+[builtins fixtures/bool.pyi]
+
+[case testEnumFinalSubtypingMethodAndValueSpecialCase]
+from enum import Enum, IntEnum, Flag, IntFlag, EnumMeta
+
+def decorator(func):
+    return func
+
+class NonEmptyEnum(Enum):
+    x = 1
+    def method(self) -> None: pass
+    @decorator
+    def other(self) -> None: pass
+class NonEmptyIntEnum(IntEnum):
+    x = 1
+    def method(self) -> None: pass
+class NonEmptyFlag(Flag):
+    x = 1
+    def method(self) -> None: pass
+class NonEmptyIntFlag(IntFlag):
+    x = 1
+    def method(self) -> None: pass
+
+class ErrorEnumWithoutValue(NonEmptyEnum):  # E: Cannot inherit from final class "NonEmptyEnum"
+    pass
+class ErrorIntEnumWithoutValue(NonEmptyIntEnum):  # E: Cannot inherit from final class "NonEmptyIntEnum"
+    pass
+class ErrorFlagWithoutValue(NonEmptyFlag):  # E: Cannot inherit from final class "NonEmptyFlag"
+    pass
+class ErrorIntFlagWithoutValue(NonEmptyIntFlag):  # E: Cannot inherit from final class "NonEmptyIntFlag"
+    pass
+[builtins fixtures/bool.pyi]
+
+[case testFinalEnumWithClassDef]
+from enum import Enum
+
+class A(Enum):
+    class Inner: pass
+class B(A): pass  # E: Cannot inherit from final class "A"
+[builtins fixtures/bool.pyi]