diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py index ff106c1b3415668..e0223e8d9351661 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 fbdbaefb399193d..0e08eda906a6992 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; @@ -189,7 +192,7 @@ SRE(charset_loc_ignore)(SRE_STATE* state, const SRE_CODE* set, SRE_CODE ch) lo = sre_lower_locale(ch); up = sre_upper_locale(ch); if (up == lo) - return SRE(charset)(state, set, lo); + return SRE(charset)(state, set, lo, ch); for (;;) { switch (*set++) { @@ -264,7 +267,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; @@ -809,7 +812,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++; @@ -879,7 +882,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++; @@ -889,7 +892,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++; @@ -928,7 +931,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); @@ -1888,7 +1891,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;