Skip to content

Commit 6b33a9c

Browse files
gh-153044: Note the flag-context invariant of the complement rule
Unicode \w and ascii \W overlap, so a category and its complement are only disjoint within one flag context; this holds because the walk never compares atoms across a flag-scoping boundary. Add tests pinning that (?a:\w+)\W and \w+(?a:\W) are not possessified, while \w+\W and (?a:\w+\W) are. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c6b58ec commit 6b33a9c

2 files changed

Lines changed: 7 additions & 1 deletion

File tree

Lib/re/_optimizer.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,10 @@ def _disjoint(atom, other, flags):
644644
ca = _as_single_category(*atom)
645645
if ca is not None:
646646
cb = _as_single_category(*other)
647-
# a category and its complement are disjoint whatever they mean
647+
# A category and its complement are disjoint whatever they mean --
648+
# but only within one flag context (unicode \w and ascii \W
649+
# overlap), which holds because the walk never compares atoms
650+
# across a flag-scoping boundary.
648651
if cb is not None and cb == CH_NEGATE[ca]:
649652
return True
650653
if not (flags & SRE_FLAG_LOCALE):

Lib/test/test_re.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3102,6 +3102,7 @@ def is_possessive(self, pattern, flags=0):
31023102
# a category and its complement are disjoint whatever they mean
31033103
(r'\p{Lu}+\P{Lu}', 0), (r'\P{Cased}+\p{Cased}', 0),
31043104
(r'\p{Lu}+\P{Lu}', re.I), (r'\d+\D', 0),
3105+
(r'\w+\W', 0), (r'(?a:\w+\W)', 0),
31053106
])
31063107
def test_possessified(self, pattern, flags):
31073108
self.assertTrue(self.is_possessive(pattern, flags))
@@ -3113,6 +3114,8 @@ def test_possessified(self, pattern, flags):
31133114
(r'a+\b', 0), (r'a+\B', 0), # word boundary
31143115
(r'a+(?=a)', 0), (r'a+(?!b)', 0), # lookaround
31153116
(r'(?i:a)+A', 0), (r'a+(?i:A)', 0), # scoped flags
3117+
# not complement pairs: unicode \w and ascii \W overlap (e.g. é)
3118+
(r'(?a:\w+)\W', 0), (r'\w+(?a:\W)', 0),
31163119
(b'a+B', re.L | re.I), # runtime locale folding
31173120
(r'a+?b', 0), # lazy
31183121
(r'[a-z--b]+c', 0), (r'[\w--\d]+\w', 0), # follower in the set

0 commit comments

Comments
 (0)