Skip to content

Commit c40cfc9

Browse files
kivikakkwhitequark
authored andcommitted
lib.enum: honor enum.nonmember.
Use _EnumDict._member_names to determine which members to consider. This way we don't need to redo sunder/dunder checks, and `nonmember`s (introduced in py3.11) are correctly excluded. This is a defacto public API, given it remains usable from py3.8 until py3.12 inclusive. (_member_names changes from a list to a keys-only dict for performance reasons in py3.11, but they iterate the same.) In current Python main (i.e. what will most likely be 3.13), a "member_names" property is added which returns those keys.
1 parent 890e099 commit c40cfc9

File tree

2 files changed

+24
-3
lines changed

2 files changed

+24
-3
lines changed

amaranth/lib/enum.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ def __new__(metacls, name, bases, namespace, shape=None, view_class=None, **kwar
4141
# Prepare enumeration members for instantiation. This logic is unfortunately very
4242
# convoluted because it supports two very different code paths that need to share
4343
# the emitted warnings.
44-
for member_name, member_value in namespace.items():
45-
if py_enum._is_sunder(member_name) or py_enum._is_dunder(member_name):
46-
continue
44+
# TODO(py3.13): can use `namespace.member_names` property.
45+
for member_name in namespace._member_names:
46+
member_value = namespace[member_name]
4747
# If a shape is specified ("Amaranth mode" of amaranth.lib.enum.Enum), then every
4848
# member value must be a constant-castable expression. Otherwise ("Python mode" of
4949
# amaranth.lib.enum.Enum) any value goes, since all enumerations accepted by

tests/test_lib_enum.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import enum as py_enum
22
import operator
33
import sys
4+
import unittest
45

56
from amaranth import *
67
from amaranth.lib.enum import Enum, EnumMeta, Flag, IntEnum, EnumView, FlagView
@@ -288,3 +289,23 @@ class EnumA(Enum, view_class=CustomView, shape=unsigned(2)):
288289
B = 1
289290
a = Signal(EnumA)
290291
assert isinstance(a, CustomView)
292+
293+
@unittest.skipUnless(hasattr(py_enum, "nonmember"), "Python<3.11 lacks nonmember")
294+
def test_enum_member_nonmember(self):
295+
with self.assertRaisesRegex(
296+
TypeError, r"^Value \{\} of enumeration member 'x' must.*$"
297+
):
298+
class EnumA(IntEnum, shape=4):
299+
A = 1
300+
x = {}
301+
302+
empty = {}
303+
class EnumA(IntEnum, shape=4):
304+
A = 1
305+
x = py_enum.nonmember(empty)
306+
self.assertIs(empty, EnumA.x)
307+
308+
class EnumB(IntEnum, shape=4):
309+
A = 1
310+
B = py_enum.member(2)
311+
self.assertIs(2, EnumB.B.value)

0 commit comments

Comments
 (0)