8000 Marks enums with values as implicitly final by sobolevn · Pull Request #11247 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

Marks enums with values as implicitly final #11247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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_ 8000 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(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to refactor this function, it is unrelated, but this is how it was rendered for me:
Снимок экрана 2021-10-02 в 13 04 16

Vs now:
Снимок экрана 2021-10-02 в 13 55 21

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))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a nested class definition? Would it be fine?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I've added an explicit test case for it. Any nested class makes enum final.

It mirrors the CPython's behavior:
Снимок экрана 2021-10-08 в 22 11 55

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
67F4
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