From 63c414abae02c9afaebe9d0d183aeb911de1807c Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sun, 31 Oct 2021 23:58:36 +0300 Subject: [PATCH] Marks enums with values as implicitly final (#11247) Closes #10857 --- mypy/semanal.py | 21 ++- test-data/unit/check-enum.test | 253 +++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+), 4 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 555f04a69a62c..15f9f6be59030 100644 --- 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: diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index e49ad7d8fa0e9..22d167f3487b4 100644 --- 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]