[Python-checkins] [3.11] gh-105497: [Enum] Fix flag mask inversion when unnamed flags exist (GH-106468) (#106621)

Yhg1s webhook-mailer at python.org
Tue Jul 11 08:10:06 EDT 2023


https://github.com/python/cpython/commit/318f6ae1cd3c5936062ff2d16fa4f3d1f38ea802
commit: 318f6ae1cd3c5936062ff2d16fa4f3d1f38ea802
branch: 3.11
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: Yhg1s <thomas at python.org>
date: 2023-07-11T14:10:02+02:00
summary:

[3.11] gh-105497: [Enum] Fix flag mask inversion when unnamed flags exist (GH-106468) (#106621)

gh-105497: [Enum] Fix flag mask inversion when unnamed flags exist (GH-106468)

For example:

    class Flag(enum.Flag):
        A = 0x01
        B = 0x02
        MASK = 0xff

    ~Flag.MASK is Flag(0)
(cherry picked from commit 95b7426f45edb570869a5513c142f29ed9f851a1)

Co-authored-by: Ethan Furman <ethan at stoneleaf.us>

files:
A Misc/NEWS.d/next/Library/2023-07-05-14-34-10.gh-issue-105497.HU5u89.rst
M Lib/enum.py
M Lib/test/test_enum.py

diff --git a/Lib/enum.py b/Lib/enum.py
index d59026162bdc6..1184b5b13e6d7 100644
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -1522,14 +1522,10 @@ def __xor__(self, other):
 
     def __invert__(self):
         if self._inverted_ is None:
-            if self._boundary_ is KEEP:
-                # use all bits
+            if self._boundary_ in (EJECT, KEEP):
                 self._inverted_ = self.__class__(~self._value_)
             else:
-                # use canonical bits (i.e. calculate flags not in this member)
-                self._inverted_ = self.__class__(self._singles_mask_ ^ self._value_)
-            if isinstance(self._inverted_, self.__class__):
-                self._inverted_._inverted_ = self
+                self._inverted_ = self.__class__(self._singles_mask_ & ~self._value_)
         return self._inverted_
 
     __rand__ = __and__
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
index ce8182e789b9a..442802823e7d0 100644
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -789,6 +789,89 @@ def test_default_missing_with_wrong_type_value(self):
             self.MainEnum('RED')
         self.assertIs(ctx.exception.__context__, None)
 
+    def test_closed_invert_expectations(self):
+        class ClosedAB(self.enum_type):
+            A = 1
+            B = 2
+            MASK = 3
+        A, B = ClosedAB
+        AB_MASK = ClosedAB.MASK
+        #
+        self.assertIs(~A, B)
+        self.assertIs(~B, A)
+        self.assertIs(~(A|B), ClosedAB(0))
+        self.assertIs(~AB_MASK, ClosedAB(0))
+        self.assertIs(~ClosedAB(0), (A|B))
+        #
+        class ClosedXYZ(self.enum_type):
+            X = 4
+            Y = 2
+            Z = 1
+            MASK = 7
+        X, Y, Z = ClosedXYZ
+        XYZ_MASK = ClosedXYZ.MASK
+        #
+        self.assertIs(~X, Y|Z)
+        self.assertIs(~Y, X|Z)
+        self.assertIs(~Z, X|Y)
+        self.assertIs(~(X|Y), Z)
+        self.assertIs(~(X|Z), Y)
+        self.assertIs(~(Y|Z), X)
+        self.assertIs(~(X|Y|Z), ClosedXYZ(0))
+        self.assertIs(~XYZ_MASK, ClosedXYZ(0))
+        self.assertIs(~ClosedXYZ(0), (X|Y|Z))
+
+    def test_open_invert_expectations(self):
+        class OpenAB(self.enum_type):
+            A = 1
+            B = 2
+            MASK = 255
+        A, B = OpenAB
+        AB_MASK = OpenAB.MASK
+        #
+        if OpenAB._boundary_ in (EJECT, KEEP):
+            self.assertIs(~A, OpenAB(254))
+            self.assertIs(~B, OpenAB(253))
+            self.assertIs(~(A|B), OpenAB(252))
+            self.assertIs(~AB_MASK, OpenAB(0))
+            self.assertIs(~OpenAB(0), AB_MASK)
+        else:
+            self.assertIs(~A, B)
+            self.assertIs(~B, A)
+            self.assertIs(~(A|B), OpenAB(0))
+            self.assertIs(~AB_MASK, OpenAB(0))
+            self.assertIs(~OpenAB(0), (A|B))
+        #
+        class OpenXYZ(self.enum_type):
+            X = 4
+            Y = 2
+            Z = 1
+            MASK = 31
+        X, Y, Z = OpenXYZ
+        XYZ_MASK = OpenXYZ.MASK
+        #
+        if OpenXYZ._boundary_ in (EJECT, KEEP):
+            self.assertIs(~X, OpenXYZ(27))
+            self.assertIs(~Y, OpenXYZ(29))
+            self.assertIs(~Z, OpenXYZ(30))
+            self.assertIs(~(X|Y), OpenXYZ(25))
+            self.assertIs(~(X|Z), OpenXYZ(26))
+            self.assertIs(~(Y|Z), OpenXYZ(28))
+            self.assertIs(~(X|Y|Z), OpenXYZ(24))
+            self.assertIs(~XYZ_MASK, OpenXYZ(0))
+            self.assertTrue(~OpenXYZ(0), XYZ_MASK)
+        else:
+            self.assertIs(~X, Y|Z)
+            self.assertIs(~Y, X|Z)
+            self.assertIs(~Z, X|Y)
+            self.assertIs(~(X|Y), Z)
+            self.assertIs(~(X|Z), Y)
+            self.assertIs(~(Y|Z), X)
+            self.assertIs(~(X|Y|Z), OpenXYZ(0))
+            self.assertIs(~XYZ_MASK, OpenXYZ(0))
+            self.assertTrue(~OpenXYZ(0), (X|Y|Z))
+
+
 class TestPlainEnum(_EnumTests, _PlainOutputTests, unittest.TestCase):
     enum_type = Enum
 
@@ -2913,33 +2996,6 @@ class Color(Flag):
         WHITE = RED|GREEN|BLUE
         BLANCO = RED|GREEN|BLUE
 
-    class Complete(Flag):
-        A = 0x01
-        B = 0x02
-
-    class Partial(Flag):
-        A = 0x01
-        B = 0x02
-        MASK = 0xff
-
-    class CompleteInt(IntFlag):
-        A = 0x01
-        B = 0x02
-
-    class PartialInt(IntFlag):
-        A = 0x01
-        B = 0x02
-        MASK = 0xff
-
-    class CompleteIntStrict(IntFlag, boundary=STRICT):
-        A = 0x01
-        B = 0x02
-
-    class PartialIntStrict(IntFlag, boundary=STRICT):
-        A = 0x01
-        B = 0x02
-        MASK = 0xff
-
     def test_or(self):
         Perm = self.Perm
         for i in Perm:
@@ -2983,34 +3039,6 @@ def test_xor(self):
         self.assertIs(Open.RO ^ Open.CE, Open.CE)
         self.assertIs(Open.CE ^ Open.CE, Open.RO)
 
-    def test_invert(self):
-        Perm = self.Perm
-        RW = Perm.R | Perm.W
-        RX = Perm.R | Perm.X
-        WX = Perm.W | Perm.X
-        RWX = Perm.R | Perm.W | Perm.X
-        values = list(Perm) + [RW, RX, WX, RWX, Perm(0)]
-        for i in values:
-            self.assertIs(type(~i), Perm)
-            self.assertEqual(~~i, i)
-        for i in Perm:
-            self.assertIs(~~i, i)
-        Open = self.Open
-        self.assertIs(Open.WO & ~Open.WO, Open.RO)
-        self.assertIs((Open.WO|Open.CE) & ~Open.WO, Open.CE)
-        Complete = self.Complete
-        self.assertIs(~Complete.A, Complete.B)
-        Partial = self.Partial
-        self.assertIs(~Partial.A, Partial.B)
-        CompleteInt = self.CompleteInt
-        self.assertIs(~CompleteInt.A, CompleteInt.B)
-        PartialInt = self.PartialInt
-        self.assertIs(~PartialInt.A, PartialInt(254))
-        CompleteIntStrict = self.CompleteIntStrict
-        self.assertIs(~CompleteIntStrict.A, CompleteIntStrict.B)
-        PartialIntStrict = self.PartialIntStrict
-        self.assertIs(~PartialIntStrict.A, PartialIntStrict.B)
-
     def test_bool(self):
         Perm = self.Perm
         for f in Perm:
diff --git a/Misc/NEWS.d/next/Library/2023-07-05-14-34-10.gh-issue-105497.HU5u89.rst b/Misc/NEWS.d/next/Library/2023-07-05-14-34-10.gh-issue-105497.HU5u89.rst
new file mode 100644
index 0000000000000..f4f2db08f73f5
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-07-05-14-34-10.gh-issue-105497.HU5u89.rst
@@ -0,0 +1 @@
+Fix flag mask inversion when unnamed flags exist.



More information about the Python-checkins mailing list