Skip to content

Commit

Permalink
[3.12] gh-105497: [Enum] Fix flag mask inversion when unnamed flags e…
Browse files Browse the repository at this point in the history
…xist (GH-106468) (#106620)

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 95b7426)

Co-authored-by: Ethan Furman <ethan@stoneleaf.us>
  • Loading branch information
miss-islington and ethanfurman authored Jul 11, 2023
1 parent 2eb9fe9 commit 6968f9e
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 61 deletions.
8 changes: 2 additions & 6 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -1539,14 +1539,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__
Expand Down
138 changes: 83 additions & 55 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,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

Expand Down Expand Up @@ -3045,33 +3128,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:
Expand Down Expand Up @@ -3115,34 +3171,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:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix flag mask inversion when unnamed flags exist.

0 comments on commit 6968f9e

Please sign in to comment.