Skip to content

Commit

Permalink
Check the order for an enum's base classes.
Browse files Browse the repository at this point in the history
Derived from https://docs.python.org/3/library/enum.html#restricted-enum-subclassing

It's not actually documented what a "data-type" is; from what I can glean from the library's source code, it's a class that defines its own `__new__` method. This is annoying, and I don't think it's very valuable to check it.

PiperOrigin-RevId: 398826650
  • Loading branch information
Solumin authored and rchen152 committed Sep 27, 2021
1 parent 3f0daf1 commit 9e1a704
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 2 deletions.
5 changes: 3 additions & 2 deletions pytype/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -890,10 +890,11 @@ def invalid_function_call(self, stack, error):
raise AssertionError(error)

@_error_name("base-class-error")
def base_class_error(self, stack, base_var):
def base_class_error(self, stack, base_var, details=None):
base_cls = self._join_printed_types(
self._print_as_expected_type(t) for t in base_var.data)
self.error(stack, "Invalid base class: %s" % base_cls, keyword=base_cls)
self.error(stack, "Invalid base class: %s" % base_cls,
details=details, keyword=base_cls)

@_error_name("bad-return-type")
def bad_return_type(self, stack, node, formal, actual, bad):
Expand Down
12 changes: 12 additions & 0 deletions pytype/overlays/enum_overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ def make_class(self, node, name_var, bases, class_dict_var, cls_var,
# errors. EnumMeta turns the class into a full enum, but that's too late for
# proper error checking.
# TODO(tsudol): Check enum validity.

# Enums have a specific ordering for base classes:
# https://docs.python.org/3/library/enum.html#restricted-enum-subclassing
# Mostly, we just care that the last base is some kind of Enum.
if not bases:
# This should be impossible.
bases = [self.to_variable(node)]
elif not any(b.is_enum for b in bases[-1].data):
msg = ("The last base class for an enum must be enum.Enum or a subclass "
"of enum.Enum")
self.vm.errorlog.base_class_error(self.vm.frames, bases[-1], details=msg)
return node, self.vm.new_unsolvable(node)
cls_var = cls_var or self.vm.loaded_overlays["enum"].members["EnumMeta"]
return self.vm.make_class(node, name_var, bases, class_dict_var, cls_var,
new_class_var, class_type=EnumInstance)
Expand Down
7 changes: 7 additions & 0 deletions pytype/tests/test_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -1048,5 +1048,12 @@ class NoFn(enum.Enum):
foo.NoFn.x # attribute-error
""", pythonpath=[d.path])

def test_enum_bases(self):
self.CheckWithErrors("""
import enum
class BadBaseOrder(enum.Enum, int): # base-class-error
A = 1
""")

if __name__ == "__main__":
test_base.main()

0 comments on commit 9e1a704

Please sign in to comment.