diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py index af6e4612dcfaef5..634520377412d71 100644 --- a/Lib/test/test_re.py +++ b/Lib/test/test_re.py @@ -977,6 +977,20 @@ def test_property_escapes(self): self.assertTrue(re.fullmatch(r'[\p{Lu}\p{Nd}]+', 'AB12')) self.assertIsNone(re.fullmatch(r'[\p{Lu}\p{Nd}]+', 'ab')) + # IGNORECASE applies to cased literals in a character class, but + # Unicode property category predicates are evaluated against the + # original input character. + self.assertEqual(re.findall(r'[\p{Lu}a]', 'AaBb1', re.I), + list('AaB')) + self.assertEqual(re.findall(r'[\P{Lu}a]', 'AaBb1', re.I), + list('Aab1')) + self.assertTrue(re.fullmatch(r'[a||\p{Lu}]', 'B', re.I)) + self.assertEqual(re.findall(r'[a--\p{Lu}]', 'AaB', re.I), + list('a')) + self.assertTrue(re.fullmatch(r'(?:\p{Lu}|a)', 'B', re.I)) + self.assertTrue(re.fullmatch(r'(?i:[\p{Lu}a])', 'B')) + self.assertIsNone(re.fullmatch(r'(?-i:[\p{Lu}a])', 'b', re.I)) + # XID_Start and XID_Continue. self.assertTrue(re.fullmatch(r'\p{XID_Start}+', 'fo\xf6Д')) self.assertIsNone(re.fullmatch(r'\p{XID_Start}', '1')) diff --git a/Misc/NEWS.d/next/Library/2026-08-06-19-35-00.gh-issue-155297.0lH-kJ.rst b/Misc/NEWS.d/next/Library/2026-08-06-19-35-00.gh-issue-155297.0lH-kJ.rst new file mode 100644 index 000000000000000..7424b8a24ad7b15 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-06-19-35-00.gh-issue-155297.0lH-kJ.rst @@ -0,0 +1,2 @@ +Fix incorrect ``\p{...}`` matches in character classes with +:const:`~re.IGNORECASE`. diff --git a/Modules/_sre/sre_lib.h b/Modules/_sre/sre_lib.h index 444cd39d2fed288..d82388ce60c6f2a 100644 --- a/Modules/_sre/sre_lib.h +++ b/Modules/_sre/sre_lib.h @@ -89,7 +89,8 @@ SRE(at)(SRE_STATE* state, const SRE_CHAR* ptr, SRE_CODE at) } LOCAL(int) -SRE(charset)(SRE_STATE* state, const SRE_CODE* set, SRE_CODE ch) +SRE(charset)(SRE_STATE* state, const SRE_CODE* set, SRE_CODE ch, + SRE_CODE original_ch) { /* check if character is a member of the given set */ @@ -110,7 +111,9 @@ SRE(charset)(SRE_STATE* state, const SRE_CODE* set, SRE_CODE ch) case SRE_OP_CATEGORY: /* */ - if (sre_category(set[0], (int) ch)) + /* Unicode property categories are not case-folded. */ + if (sre_category(set[0], (int) (set[0] >= SRE_CATEGORY_ALPHA ? + original_ch : ch))) return ok; set++; break; @@ -180,11 +183,11 @@ SRE(charset_loc_ignore)(SRE_STATE* state, const SRE_CODE* set, SRE_CODE ch) { SRE_CODE lo, up; lo = sre_lower_locale(ch); - if (SRE(charset)(state, set, lo)) + if (SRE(charset)(state, set, lo, ch)) return 1; up = sre_upper_locale(ch); - return up != lo && SRE(charset)(state, set, up); + return up != lo && SRE(charset)(state, set, up, ch); } LOCAL(Py_ssize_t) SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel); @@ -209,7 +212,7 @@ SRE(count)(SRE_STATE* state, const SRE_CODE* pattern, Py_ssize_t maxcount) case SRE_OP_IN: /* repeated set */ TRACE(("|%p|%p|COUNT IN\n", pattern, ptr)); - while (ptr < end && SRE(charset)(state, pattern + 2, *ptr)) + while (ptr < end && SRE(charset)(state, pattern + 2, *ptr, *ptr)) ptr++; break; @@ -754,7 +757,7 @@ SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel) /* */ TRACE(("|%p|%p|IN\n", pattern, ptr)); if (ptr >= end || - !SRE(charset)(state, pattern + 1, *ptr)) + !SRE(charset)(state, pattern + 1, *ptr, *ptr)) RETURN_FAILURE; pattern += pattern[0]; ptr++; @@ -824,7 +827,7 @@ SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel) TRACE(("|%p|%p|IN_IGNORE\n", pattern, ptr)); if (ptr >= end || !SRE(charset)(state, pattern+1, - (SRE_CODE)sre_lower_ascii(*ptr))) + (SRE_CODE)sre_lower_ascii(*ptr), *ptr)) RETURN_FAILURE; pattern += pattern[0]; ptr++; @@ -834,7 +837,7 @@ SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel) TRACE(("|%p|%p|IN_UNI_IGNORE\n", pattern, ptr)); if (ptr >= end || !SRE(charset)(state, pattern+1, - (SRE_CODE)sre_lower_unicode(*ptr))) + (SRE_CODE)sre_lower_unicode(*ptr), *ptr)) RETURN_FAILURE; pattern += pattern[0]; ptr++; @@ -873,7 +876,7 @@ SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel) if (pattern[1] == SRE_OP_IN && (ptr >= end || !SRE(charset)(state, pattern + 3, - (SRE_CODE) *ptr))) + (SRE_CODE) *ptr, (SRE_CODE) *ptr))) continue; state->ptr = ptr; DO_JUMP(JUMP_BRANCH, jump_branch, pattern+1); @@ -1833,7 +1836,7 @@ SRE(search)(SRE_STATE* state, SRE_CODE* pattern) end = (SRE_CHAR *)state->end; state->must_advance = 0; for (;;) { - while (ptr < end && !SRE(charset)(state, charset, *ptr)) + while (ptr < end && !SRE(charset)(state, charset, *ptr, *ptr)) ptr++; if (ptr >= end) return 0;