Skip to content

Commit

Permalink
Marks enums with values as implicitly final (#11247)
Browse files Browse the repository at this point in the history
Closes #10857
  • Loading branch information
sobolevn authored Oct 31, 2021
1 parent f4a21a4 commit 63c414a
Show file tree
Hide file tree
Showing 2 changed files with 270 additions and 4 deletions.
21 changes: 17 additions & 4 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down
253 changes: 253 additions & 0 deletions test-data/unit/check-enum.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]

0 comments on commit 63c414a

Please sign in to comment.