Skip to content

Commit 49365bd

Browse files
authored
gh-107538: [Enum] fix handling of inverted/negative values (GH-132273)
* Fix flag mask inversion when unnamed flags exist. For example: class Flag(enum.Flag): A = 0x01 B = 0x02 MASK = 0xff ~Flag.MASK is Flag(0) * EJECT and KEEP flags (IntEnum is KEEP) use direct value. * correct Flag inversion to only flip flag bits IntFlag will flip all bits -- this only makes a difference in flag sets with missing values. * correct negative assigned values in flags negative values are no longer used as-is, but become inverted; i.e. class Y(self.enum_type): A = auto() B = auto() C = ~A # aka ~1 aka 0b1 110 (from enum.bin()) aka 6 D = auto() assert Y.C. is Y.B|Y.D
1 parent 56c6f04 commit 49365bd

File tree

4 files changed

+70
-2
lines changed

4 files changed

+70
-2
lines changed

Lib/enum.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k
535535
# now set the __repr__ for the value
536536
classdict['_value_repr_'] = metacls._find_data_repr_(cls, bases)
537537
#
538-
# Flag structures (will be removed if final class is not a Flag
538+
# Flag structures (will be removed if final class is not a Flag)
539539
classdict['_boundary_'] = (
540540
boundary
541541
or getattr(first_enum, '_boundary_', None)
@@ -544,6 +544,29 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k
544544
classdict['_singles_mask_'] = 0
545545
classdict['_all_bits_'] = 0
546546
classdict['_inverted_'] = None
547+
# check for negative flag values and invert if found (using _proto_members)
548+
if Flag is not None and bases and issubclass(bases[-1], Flag):
549+
bits = 0
550+
inverted = []
551+
for n in member_names:
552+
p = classdict[n]
553+
if isinstance(p.value, int):
554+
if p.value < 0:
555+
inverted.append(p)
556+
else:
557+
bits |= p.value
558+
elif p.value is None:
559+
pass
560+
elif isinstance(p.value, tuple) and p.value and isinstance(p.value[0], int):
561+
if p.value[0] < 0:
562+
inverted.append(p)
563+
else:
564+
bits |= p.value[0]
565+
for p in inverted:
566+
if isinstance(p.value, int):
567+
p.value = bits & p.value
568+
else:
569+
p.value = (bits & p.value[0], ) + p.value[1:]
547570
try:
548571
classdict['_%s__in_progress' % cls] = True
549572
enum_class = super().__new__(metacls, cls, bases, classdict, **kwds)
@@ -1487,7 +1510,10 @@ def _missing_(cls, value):
14871510
)
14881511
if value < 0:
14891512
neg_value = value
1490-
value = all_bits + 1 + value
1513+
if cls._boundary_ in (EJECT, KEEP):
1514+
value = all_bits + 1 + value
1515+
else:
1516+
value = singles_mask & value
14911517
# get members and unknown
14921518
unknown = value & ~flag_mask
14931519
aliases = value & ~singles_mask

Lib/test/test_enum.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,12 +1002,18 @@ class OpenAB(self.enum_type):
10021002
self.assertIs(~(A|B), OpenAB(252))
10031003
self.assertIs(~AB_MASK, OpenAB(0))
10041004
self.assertIs(~OpenAB(0), AB_MASK)
1005+
self.assertIs(OpenAB(~4), OpenAB(251))
10051006
else:
10061007
self.assertIs(~A, B)
10071008
self.assertIs(~B, A)
1009+
self.assertIs(OpenAB(~1), B)
1010+
self.assertIs(OpenAB(~2), A)
10081011
self.assertIs(~(A|B), OpenAB(0))
10091012
self.assertIs(~AB_MASK, OpenAB(0))
10101013
self.assertIs(~OpenAB(0), (A|B))
1014+
self.assertIs(OpenAB(~3), OpenAB(0))
1015+
self.assertIs(OpenAB(~4), OpenAB(3))
1016+
self.assertIs(OpenAB(~33), B)
10111017
#
10121018
class OpenXYZ(self.enum_type):
10131019
X = 4
@@ -1031,13 +1037,38 @@ class OpenXYZ(self.enum_type):
10311037
self.assertIs(~X, Y|Z)
10321038
self.assertIs(~Y, X|Z)
10331039
self.assertIs(~Z, X|Y)
1040+
self.assertIs(OpenXYZ(~4), Y|Z)
1041+
self.assertIs(OpenXYZ(~2), X|Z)
1042+
self.assertIs(OpenXYZ(~1), X|Y)
10341043
self.assertIs(~(X|Y), Z)
10351044
self.assertIs(~(X|Z), Y)
10361045
self.assertIs(~(Y|Z), X)
10371046
self.assertIs(~(X|Y|Z), OpenXYZ(0))
10381047
self.assertIs(~XYZ_MASK, OpenXYZ(0))
10391048
self.assertTrue(~OpenXYZ(0), (X|Y|Z))
10401049

1050+
def test_assigned_negative_value(self):
1051+
class X(self.enum_type):
1052+
A = auto()
1053+
B = auto()
1054+
C = A | B
1055+
D = ~A
1056+
self.assertEqual(list(X), [X.A, X.B])
1057+
self.assertIs(~X.A, X.B)
1058+
self.assertIs(X.D, X.B)
1059+
self.assertEqual(X.D.value, 2)
1060+
#
1061+
class Y(self.enum_type):
1062+
A = auto()
1063+
B = auto()
1064+
C = A | B
1065+
D = ~A
1066+
E = auto()
1067+
self.assertEqual(list(Y), [Y.A, Y.B, Y.E])
1068+
self.assertIs(~Y.A, Y.B|Y.E)
1069+
self.assertIs(Y.D, Y.B|Y.E)
1070+
self.assertEqual(Y.D.value, 6)
1071+
10411072

10421073
class TestPlainEnumClass(_EnumTests, _PlainOutputTests, unittest.TestCase):
10431074
enum_type = Enum
@@ -3680,6 +3711,8 @@ class SkipFlag(enum.Flag):
36803711
C = 4 | B
36813712
#
36823713
self.assertTrue(SkipFlag.C in (SkipFlag.A|SkipFlag.C))
3714+
self.assertTrue(SkipFlag.B in SkipFlag.C)
3715+
self.assertIs(SkipFlag(~1), SkipFlag.B)
36833716
self.assertRaisesRegex(ValueError, 'SkipFlag.. invalid value 42', SkipFlag, 42)
36843717
#
36853718
class SkipIntFlag(enum.IntFlag):
@@ -3688,6 +3721,8 @@ class SkipIntFlag(enum.IntFlag):
36883721
C = 4 | B
36893722
#
36903723
self.assertTrue(SkipIntFlag.C in (SkipIntFlag.A|SkipIntFlag.C))
3724+
self.assertTrue(SkipIntFlag.B in SkipIntFlag.C)
3725+
self.assertIs(SkipIntFlag(~1), SkipIntFlag.B|SkipIntFlag.C)
36913726
self.assertEqual(SkipIntFlag(42).value, 42)
36923727
#
36933728
class MethodHint(Flag):
@@ -4727,6 +4762,8 @@ class Color(Flag):
47274762
BLUE = 4
47284763
WHITE = -1
47294764
# no error means success
4765+
self.assertEqual(list(Color.WHITE), [Color.RED, Color.GREEN, Color.BLUE])
4766+
self.assertEqual(Color.WHITE.value, 7)
47304767

47314768

47324769
class TestInternals(unittest.TestCase):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix flag mask inversion when unnamed flags exist.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :class:`!Flag` inversion when flag set has missing values
2+
(:class:`!IntFlag` still flips all bits); fix negative assigned values
3+
during flag creation (both :class:`!Flag` and :class:`!IntFlag` ignore
4+
missing values).

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy