Skip to content
Closed
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
3 changes: 1 addition & 2 deletions mypyc/irbuild/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -973,8 +973,7 @@ def get_final_ref(self, expr: MemberExpr) -> tuple[str, Var, bool] | None:
sym = expr.expr.node.get(expr.name)
if sym and isinstance(sym.node, Var):
# Enum attribute are treated as final since they are added to the global cache
expr_fullname = expr.expr.node.bases[0].type.fullname
is_final = sym.node.is_final or expr_fullname == "enum.Enum"
is_final = sym.node.is_final or expr.expr.node.is_enum
if is_final:
final_var = sym.node
fullname = f"{sym.node.info.fullname}.{final_var.name}"
Expand Down
5 changes: 3 additions & 2 deletions mypyc/irbuild/classdef.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,12 +467,13 @@ def populate_non_ext_bases(builder: IRBuilder, cdef: ClassDef) -> Value:
The tuple is passed to the metaclass constructor.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

should docstring change in this case ?

"""
is_named_tuple = cdef.info.is_named_tuple
is_enum = cdef.info.is_enum
ir = builder.mapper.type_to_ir[cdef.info]
bases = []
for cls in cdef.info.mro[1:]:
if cls.fullname == "builtins.object":
continue
if is_named_tuple and cls.fullname in (
if (is_named_tuple or is_enum) and cls.fullname in (

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

My thoughts about this change are as the following:

  1. is_extension_class will return False for StrEnum as metaclass_type is set to enum.EnumMeta
    if cdef.info.metaclass_type and cdef.info.metaclass_type.type.fullname not in (
  2. transform_class_def mentioned explicitly in comments that classes inheriting from enum will have non-extension class object created for them
    # decorated or inherit from Enum. Classes decorated with @trait do not
  3. For some reason populate_non_ext_bases is explicitly checking for named tuple though any class inheriting say str or have a typing.Sequence in mro it will cause the error currently seen in building code with StrEnum
    if is_named_tuple and cls.fullname in (

Not really sure if checking against both Enums and Named tuples is generic enough or not.

"typing.Sequence",
"typing.Iterable",
"typing.Collection",
Expand Down Expand Up @@ -630,7 +631,7 @@ def add_non_ext_class_attr(
# are final.
if (
cdef.info.bases
and cdef.info.bases[0].type.fullname == "enum.Enum"
and cdef.info.bases[0].type.is_enum
# Skip these since Enum will remove it
and lvalue.name not in ENUM_REMOVED_PROPS
):
Expand Down
55 changes: 51 additions & 4 deletions mypyc/test-data/run-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,11 @@ assert o.get(20) == 20
assert get_class_var() == 'xy'

[case testEnum]
from enum import Enum
import enum
import sys, typing_extensions


class TestEnum(Enum):
class TestEnum(enum.Enum):
_order_ = "a b"
a : int = 1
b : int = 2
Expand All @@ -263,8 +265,6 @@ class TestEnum(Enum):

assert TestEnum.test() == 3

import enum

class Pokemon(enum.Enum):
magikarp = 1
squirtle = 2
Expand All @@ -273,12 +273,47 @@ class Pokemon(enum.Enum):
assert Pokemon.magikarp.value == 1
assert Pokemon.squirtle.name == 'squirtle'

# skip test on python 3.8 as it does not allow multiple data-type mixins of the same type (see bpo-39587)
if sys.version_info[:2] != (3, 8):
int_enum_base_cls: typing_extensions.TypeAlias = enum.IntEnum
int_flag_enum_blase_cls: typing_extensions.TypeAlias = enum.IntFlag
else:
int_enum_base_cls: typing_extensions.TypeAlias = enum.Enum
int_flag_enum_blase_cls: typing_extensions.TypeAlias = enum.Enum

class Color(int_enum_base_cls):
GREEN = 1

if sys.version_info[:2] != (3, 8):
assert Color.GREEN.value == 1

if sys.version_info[0] >= 3 and sys.version_info[1] >= 11:
str_enum_base_cls: typing_extensions.TypeAlias = enum.StrEnum
else:
str_enum_base_cls: typing_extensions.TypeAlias = enum.Enum

class BuildEnum(str_enum_base_cls):
DEBUG = "debug"
RELEASE = "release"

class ColorFlagEnum(enum.Flag):
RED = enum.auto()
GREEN = enum.auto()
BLUE = enum.auto()

class ColorIntFlagEnum(int_flag_enum_blase_cls):
RED = enum.auto()
GREEN = enum.auto()
BLUE = enum.auto()


[file other.py]
# Force a multi-module test to make sure we can compile multi-file with
# non-extension classes

[file driver.py]
import sys
from native import ColorFlagEnum
# "_order_" isn't supported in 3.5
if sys.version_info[:2] > (3, 5):
from native import TestEnum
Expand All @@ -287,6 +322,18 @@ if sys.version_info[:2] > (3, 5):
assert TestEnum.b.name == 'b'
assert TestEnum.b.value == 2

# StrEnum is available in 3.11
if sys.version_info[:2] >= (3, 11):
from native import BuildEnum
assert BuildEnum.DEBUG == "debug"

purple = ColorFlagEnum.RED | ColorFlagEnum.BLUE
assert ColorFlagEnum.RED in purple

if sys.version_info[:2] != (3, 8):
from native import ColorIntFlagEnum
assert ColorIntFlagEnum.GREEN == ColorIntFlagEnum.RED + 1

[case testGetAttribute]
class C:
x: int
Expand Down