Skip to content

Commit

Permalink
Check Enum definition for invalid base classes (#12026)
Browse files Browse the repository at this point in the history
Closes #11948
  • Loading branch information
sobolevn authored Feb 22, 2022
1 parent a8b6d6f commit 1321e9e
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 0 deletions.
33 changes: 33 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1837,6 +1837,7 @@ def visit_class_def(self, defn: ClassDef) -> None:
for base in defn.info.mro[1:-1]: # we don't need self and `object`
if base.is_enum and base.fullname not in ENUM_BASES:
self.check_final_enum(defn, base)
self.check_enum_bases(defn)

def check_final_deletable(self, typ: TypeInfo) -> None:
# These checks are only for mypyc. Only perform some checks that are easier
Expand Down Expand Up @@ -1931,6 +1932,38 @@ def is_final_enum_value(self, sym: SymbolTableNode) -> bool:
return True
return False

def check_enum_bases(self, defn: ClassDef) -> None:
enum_base: Optional[Instance] = None
data_base: Optional[Instance] = None
for base in defn.info.bases:
if enum_base is None and base.type.fullname in ENUM_BASES:
enum_base = base
continue
elif enum_base is not None:
self.fail(
'No base classes are allowed after "{}"'.format(enum_base),
defn,
)
break

# This might not be 100% correct, because runtime `__new__`
# and `__new__` from `typeshed` are sometimes different,
# but it is good enough.
new_method = base.type.get('__new__')
if (data_base is None
and enum_base is None # data type is always before `Enum`
and new_method
and new_method.node
and new_method.node.fullname != 'builtins.object.__new__'):
data_base = base
continue
elif data_base is not None:
self.fail(
'Only a single data type mixin is allowed for Enum subtypes, '
'found extra "{}"'.format(base),
defn,
)

def check_protocol_variance(self, defn: ClassDef) -> None:
"""Check that protocol definition is compatible with declared
variances of type variables.
Expand Down
65 changes: 65 additions & 0 deletions test-data/unit/check-enum.test
Original file line number Diff line number Diff line change
Expand Up @@ -1883,6 +1883,71 @@ class SubWithOverload(WithOverload): # Should pass
pass
[builtins fixtures/tuple.pyi]

[case testEnumBaseClassesOrder]
import enum

# Base types:

class First:
def __new__(cls, val):
pass

class Second:
def __new__(cls, val):
pass

class Third:
def __new__(cls, val):
pass

class Mixin:
pass

# Correct Enums:

class Correct1(Mixin, First, enum.Enum):
pass

class Correct2(First, enum.Enum):
pass

class Correct3(Mixin, enum.Enum):
pass

class RegularClass(Mixin, First, Second):
pass

# Wrong Enums:

class MixinAfterEnum1(enum.Enum, Mixin): # E: No base classes are allowed after "enum.Enum"
pass

class MixinAfterEnum2(First, enum.Enum, Mixin): # E: No base classes are allowed after "enum.Enum"
pass

class TwoDataTypes(First, Second, enum.Enum): # E: Only a single data type mixin is allowed for Enum subtypes, found extra "__main__.Second"
pass

class TwoDataTypesAndIntEnumMixin(First, Second, enum.IntEnum, Mixin): # E: Only a single data type mixin is allowed for Enum subtypes, found extra "__main__.Second" \
# E: No base classes are allowed after "enum.IntEnum"
pass

class ThreeDataTypes(First, Second, Third, enum.Enum): # E: Only a single data type mixin is allowed for Enum subtypes, found extra "__main__.Second" \
# E: # E: Only a single data type mixin is allowed for Enum subtypes, found extra "__main__.Third"
pass

class ThreeDataTypesAndMixin(First, Second, Third, enum.Enum, Mixin): # E: Only a single data type mixin is allowed for Enum subtypes, found extra "__main__.Second" \
# E: # E: Only a single data type mixin is allowed for Enum subtypes, found extra "__main__.Third" \
# E: No base classes are allowed after "enum.Enum"
pass

class FromEnumAndOther1(Correct2, Second, enum.Enum): # E: Only a single data type mixin is allowed for Enum subtypes, found extra "__main__.Second"
pass

class FromEnumAndOther2(Correct2, Second): # E: Only a single data type mixin is allowed for Enum subtypes, found extra "__main__.Second"
pass
[builtins fixtures/tuple.pyi]

[case testEnumtValueUnionSimplification]
from enum import IntEnum
from typing import Any
Expand Down

0 comments on commit 1321e9e

Please sign in to comment.