Skip to content

Commit 95b7426

Browse files
authored
gh-105497: [Enum] Fix flag mask inversion when unnamed flags exist (#106468)
For example: class Flag(enum.Flag): A = 0x01 B = 0x02 MASK = 0xff ~Flag.MASK is Flag(0)
1 parent af5cf1e commit 95b7426

File tree

3 files changed

+86
-61
lines changed

3 files changed

+86
-61
lines changed

Lib/enum.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -1515,14 +1515,10 @@ def __xor__(self, other):
15151515

15161516
def __invert__(self):
15171517
if self._inverted_ is None:
1518-
if self._boundary_ is KEEP:
1519-
# use all bits
1518+
if self._boundary_ in (EJECT, KEEP):
15201519
self._inverted_ = self.__class__(~self._value_)
15211520
else:
1522-
# use canonical bits (i.e. calculate flags not in this member)
1523-
self._inverted_ = self.__class__(self._singles_mask_ ^ self._value_)
1524-
if isinstance(self._inverted_, self.__class__):
1525-
self._inverted_._inverted_ = self
1521+
self._inverted_ = self.__class__(self._singles_mask_ & ~self._value_)
15261522
return self._inverted_
15271523

15281524
__rand__ = __and__

Lib/test/test_enum.py

+83-55
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,89 @@ def test_default_missing_with_wrong_type_value(self):
818818
self.MainEnum('RED')
819819
self.assertIs(ctx.exception.__context__, None)
820820

821+
def test_closed_invert_expectations(self):
822+
class ClosedAB(self.enum_type):
823+
A = 1
824+
B = 2
825+
MASK = 3
826+
A, B = ClosedAB
827+
AB_MASK = ClosedAB.MASK
828+
#
829+
self.assertIs(~A, B)
830+
self.assertIs(~B, A)
831+
self.assertIs(~(A|B), ClosedAB(0))
832+
self.assertIs(~AB_MASK, ClosedAB(0))
833+
self.assertIs(~ClosedAB(0), (A|B))
834+
#
835+
class ClosedXYZ(self.enum_type):
836+
X = 4
837+
Y = 2
838+
Z = 1
839+
MASK = 7
840+
X, Y, Z = ClosedXYZ
841+
XYZ_MASK = ClosedXYZ.MASK
842+
#
843+
self.assertIs(~X, Y|Z)
844+
self.assertIs(~Y, X|Z)
845+
self.assertIs(~Z, X|Y)
846+
self.assertIs(~(X|Y), Z)
847+
self.assertIs(~(X|Z), Y)
848+
self.assertIs(~(Y|Z), X)
849+
self.assertIs(~(X|Y|Z), ClosedXYZ(0))
850+
self.assertIs(~XYZ_MASK, ClosedXYZ(0))
851+
self.assertIs(~ClosedXYZ(0), (X|Y|Z))
852+
853+
def test_open_invert_expectations(self):
854+
class OpenAB(self.enum_type):
855+
A = 1
856+
B = 2
857+
MASK = 255
858+
A, B = OpenAB
859+
AB_MASK = OpenAB.MASK
860+
#
861+
if OpenAB._boundary_ in (EJECT, KEEP):
862+
self.assertIs(~A, OpenAB(254))
863+
self.assertIs(~B, OpenAB(253))
864+
self.assertIs(~(A|B), OpenAB(252))
865+
self.assertIs(~AB_MASK, OpenAB(0))
866+
self.assertIs(~OpenAB(0), AB_MASK)
867+
else:
868+
self.assertIs(~A, B)
869+
self.assertIs(~B, A)
870+
self.assertIs(~(A|B), OpenAB(0))
871+
self.assertIs(~AB_MASK, OpenAB(0))
872+
self.assertIs(~OpenAB(0), (A|B))
873+
#
874+
class OpenXYZ(self.enum_type):
875+
X = 4
876+
Y = 2
877+
Z = 1
878+
MASK = 31
879+
X, Y, Z = OpenXYZ
880+
XYZ_MASK = OpenXYZ.MASK
881+
#
882+
if OpenXYZ._boundary_ in (EJECT, KEEP):
883+
self.assertIs(~X, OpenXYZ(27))
884+
self.assertIs(~Y, OpenXYZ(29))
885+
self.assertIs(~Z, OpenXYZ(30))
886+
self.assertIs(~(X|Y), OpenXYZ(25))
887+
self.assertIs(~(X|Z), OpenXYZ(26))
888+
self.assertIs(~(Y|Z), OpenXYZ(28))
889+
self.assertIs(~(X|Y|Z), OpenXYZ(24))
890+
self.assertIs(~XYZ_MASK, OpenXYZ(0))
891+
self.assertTrue(~OpenXYZ(0), XYZ_MASK)
892+
else:
893+
self.assertIs(~X, Y|Z)
894+
self.assertIs(~Y, X|Z)
895+
self.assertIs(~Z, X|Y)
896+
self.assertIs(~(X|Y), Z)
897+
self.assertIs(~(X|Z), Y)
898+
self.assertIs(~(Y|Z), X)
899+
self.assertIs(~(X|Y|Z), OpenXYZ(0))
900+
self.assertIs(~XYZ_MASK, OpenXYZ(0))
901+
self.assertTrue(~OpenXYZ(0), (X|Y|Z))
902+
903+
821904
class TestPlainEnum(_EnumTests, _PlainOutputTests, unittest.TestCase):
822905
enum_type = Enum
823906

@@ -3045,33 +3128,6 @@ class Color(Flag):
30453128
WHITE = RED|GREEN|BLUE
30463129
BLANCO = RED|GREEN|BLUE
30473130

3048-
class Complete(Flag):
3049-
A = 0x01
3050-
B = 0x02
3051-
3052-
class Partial(Flag):
3053-
A = 0x01
3054-
B = 0x02
3055-
MASK = 0xff
3056-
3057-
class CompleteInt(IntFlag):
3058-
A = 0x01
3059-
B = 0x02
3060-
3061-
class PartialInt(IntFlag):
3062-
A = 0x01
3063-
B = 0x02
3064-
MASK = 0xff
3065-
3066-
class CompleteIntStrict(IntFlag, boundary=STRICT):
3067-
A = 0x01
3068-
B = 0x02
3069-
3070-
class PartialIntStrict(IntFlag, boundary=STRICT):
3071-
A = 0x01
3072-
B = 0x02
3073-
MASK = 0xff
3074-
30753131
def test_or(self):
30763132
Perm = self.Perm
30773133
for i in Perm:
@@ -3115,34 +3171,6 @@ def test_xor(self):
31153171
self.assertIs(Open.RO ^ Open.CE, Open.CE)
31163172
self.assertIs(Open.CE ^ Open.CE, Open.RO)
31173173

3118-
def test_invert(self):
3119-
Perm = self.Perm
3120-
RW = Perm.R | Perm.W
3121-
RX = Perm.R | Perm.X
3122-
WX = Perm.W | Perm.X
3123-
RWX = Perm.R | Perm.W | Perm.X
3124-
values = list(Perm) + [RW, RX, WX, RWX, Perm(0)]
3125-
for i in values:
3126-
self.assertIs(type(~i), Perm)
3127-
self.assertEqual(~~i, i)
3128-
for i in Perm:
3129-
self.assertIs(~~i, i)
3130-
Open = self.Open
3131-
self.assertIs(Open.WO & ~Open.WO, Open.RO)
3132-
self.assertIs((Open.WO|Open.CE) & ~Open.WO, Open.CE)
3133-
Complete = self.Complete
3134-
self.assertIs(~Complete.A, Complete.B)
3135-
Partial = self.Partial
3136-
self.assertIs(~Partial.A, Partial.B)
3137-
CompleteInt = self.CompleteInt
3138-
self.assertIs(~CompleteInt.A, CompleteInt.B)
3139-
PartialInt = self.PartialInt
3140-
self.assertIs(~PartialInt.A, PartialInt(254))
3141-
CompleteIntStrict = self.CompleteIntStrict
3142-
self.assertIs(~CompleteIntStrict.A, CompleteIntStrict.B)
3143-
PartialIntStrict = self.PartialIntStrict
3144-
self.assertIs(~PartialIntStrict.A, PartialIntStrict.B)
3145-
31463174
def test_bool(self):
31473175
Perm = self.Perm
31483176
for f in Perm:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix flag mask inversion when unnamed flags exist.

0 commit comments

Comments
 (0)