]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-117663: [Enum] fix _simple_enum's detection of aliases (GH-117664)
authorEthan Furman <ethan@stoneleaf.us>
Tue, 9 Apr 2024 18:31:07 +0000 (11:31 -0700)
committerGitHub <noreply@github.com>
Tue, 9 Apr 2024 18:31:07 +0000 (11:31 -0700)
Lib/enum.py
Lib/test/test_enum.py
Misc/NEWS.d/next/Library/2024-04-08-19-12-26.gh-issue-117663.CPfc_p.rst [new file with mode: 0644]

index 2a135e1b1f18262a9183512f9023031ded94d888..98a49eafbb98973bf1737a571f685390cffdaea7 100644 (file)
@@ -1088,8 +1088,6 @@ class EnumType(type):
             setattr(cls, name, member)
         # now add to _member_map_ (even aliases)
         cls._member_map_[name] = member
-        #
-        cls._member_map_[name] = member
 
 EnumMeta = EnumType         # keep EnumMeta name for backwards compatibility
 
@@ -1802,20 +1800,31 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None):
             for name, value in attrs.items():
                 if isinstance(value, auto) and auto.value is _auto_null:
                     value = gnv(name, 1, len(member_names), gnv_last_values)
-                if value in value2member_map or value in unhashable_values:
+                # create basic member (possibly isolate value for alias check)
+                if use_args:
+                    if not isinstance(value, tuple):
+                        value = (value, )
+                    member = new_member(enum_class, *value)
+                    value = value[0]
+                else:
+                    member = new_member(enum_class)
+                if __new__ is None:
+                    member._value_ = value
+                # now check if alias
+                try:
+                    contained = value2member_map.get(member._value_)
+                except TypeError:
+                    contained = None
+                    if member._value_ in unhashable_values:
+                        for m in enum_class:
+                            if m._value_ == member._value_:
+                                contained = m
+                                break
+                if contained is not None:
                     # an alias to an existing member
-                    enum_class(value)._add_alias_(name)
+                    contained._add_alias_(name)
                 else:
-                    # create the member
-                    if use_args:
-                        if not isinstance(value, tuple):
-                            value = (value, )
-                        member = new_member(enum_class, *value)
-                        value = value[0]
-                    else:
-                        member = new_member(enum_class)
-                    if __new__ is None:
-                        member._value_ = value
+                    # finish creating member
                     member._name_ = name
                     member.__objclass__ = enum_class
                     member.__init__(value)
@@ -1847,24 +1856,31 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None):
                     if value.value is _auto_null:
                         value.value = gnv(name, 1, len(member_names), gnv_last_values)
                     value = value.value
+                # create basic member (possibly isolate value for alias check)
+                if use_args:
+                    if not isinstance(value, tuple):
+                        value = (value, )
+                    member = new_member(enum_class, *value)
+                    value = value[0]
+                else:
+                    member = new_member(enum_class)
+                if __new__ is None:
+                    member._value_ = value
+                # now check if alias
                 try:
-                    contained = value in value2member_map
+                    contained = value2member_map.get(member._value_)
                 except TypeError:
-                    contained = value in unhashable_values
-                if contained:
+                    contained = None
+                    if member._value_ in unhashable_values:
+                        for m in enum_class:
+                            if m._value_ == member._value_:
+                                contained = m
+                                break
+                if contained is not None:
                     # an alias to an existing member
-                    enum_class(value)._add_alias_(name)
+                    contained._add_alias_(name)
                 else:
-                    # create the member
-                    if use_args:
-                        if not isinstance(value, tuple):
-                            value = (value, )
-                        member = new_member(enum_class, *value)
-                        value = value[0]
-                    else:
-                        member = new_member(enum_class)
-                    if __new__ is None:
-                        member._value_ = value
+                    # finish creating member
                     member._name_ = name
                     member.__objclass__ = enum_class
                     member.__init__(value)
index 6418d243db65ce2c0ba762afd21b62945ada1702..529dfc62eff680a1b8bd173eef7569ba893642f5 100644 (file)
@@ -5170,7 +5170,57 @@ class TestStdLib(unittest.TestCase):
         self.assertIn('python', Unhashable)
         self.assertEqual(Unhashable.name.value, 'python')
         self.assertEqual(Unhashable.name.name, 'name')
-        _test_simple_enum(Unhashable, Unhashable)
+        _test_simple_enum(CheckedUnhashable, Unhashable)
+        ##
+        class CheckedComplexStatus(IntEnum):
+            def __new__(cls, value, phrase, description=''):
+                obj = int.__new__(cls, value)
+                obj._value_ = value
+                obj.phrase = phrase
+                obj.description = description
+                return obj
+            CONTINUE = 100, 'Continue', 'Request received, please continue'
+            PROCESSING = 102, 'Processing'
+            EARLY_HINTS = 103, 'Early Hints'
+            SOME_HINTS = 103, 'Some Early Hints'
+        #
+        @_simple_enum(IntEnum)
+        class ComplexStatus:
+            def __new__(cls, value, phrase, description=''):
+                obj = int.__new__(cls, value)
+                obj._value_ = value
+                obj.phrase = phrase
+                obj.description = description
+                return obj
+            CONTINUE = 100, 'Continue', 'Request received, please continue'
+            PROCESSING = 102, 'Processing'
+            EARLY_HINTS = 103, 'Early Hints'
+            SOME_HINTS = 103, 'Some Early Hints'
+        _test_simple_enum(CheckedComplexStatus, ComplexStatus)
+        #
+        #
+        class CheckedComplexFlag(IntFlag):
+            def __new__(cls, value, label):
+                obj = int.__new__(cls, value)
+                obj._value_ = value
+                obj.label = label
+                return obj
+            SHIRT = 1, 'upper half'
+            VEST = 1, 'outer upper half'
+            PANTS = 2, 'lower half'
+        self.assertIs(CheckedComplexFlag.SHIRT, CheckedComplexFlag.VEST)
+        #
+        @_simple_enum(IntFlag)
+        class ComplexFlag:
+            def __new__(cls, value, label):
+                obj = int.__new__(cls, value)
+                obj._value_ = value
+                obj.label = label
+                return obj
+            SHIRT = 1, 'upper half'
+            VEST = 1, 'uppert half'
+            PANTS = 2, 'lower half'
+        _test_simple_enum(CheckedComplexFlag, ComplexFlag)
 
 
 class MiscTestCase(unittest.TestCase):
diff --git a/Misc/NEWS.d/next/Library/2024-04-08-19-12-26.gh-issue-117663.CPfc_p.rst b/Misc/NEWS.d/next/Library/2024-04-08-19-12-26.gh-issue-117663.CPfc_p.rst
new file mode 100644 (file)
index 0000000..2c7a522
--- /dev/null
@@ -0,0 +1,2 @@
+Fix ``_simple_enum`` to detect aliases when multiple arguments are present
+but only one is the member value.