Skip to content

Commit 1c2fceb

Browse files
pythongh-91575: Update case-insensitive matching in re to the latest Unicode version (pythonGH-91580)
1 parent 2e7e3c4 commit 1c2fceb

File tree

3 files changed

+78
-9
lines changed

3 files changed

+78
-9
lines changed

Diff for: Lib/re/_compiler.py

+27-3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,22 @@
5757
(0x3c2, 0x3c3), # ςσ
5858
# GREEK SMALL LETTER PHI, GREEK PHI SYMBOL
5959
(0x3c6, 0x3d5), # φϕ
60+
# CYRILLIC SMALL LETTER VE, CYRILLIC SMALL LETTER ROUNDED VE
61+
(0x432, 0x1c80), # вᲀ
62+
# CYRILLIC SMALL LETTER DE, CYRILLIC SMALL LETTER LONG-LEGGED DE
63+
(0x434, 0x1c81), # дᲁ
64+
# CYRILLIC SMALL LETTER O, CYRILLIC SMALL LETTER NARROW O
65+
(0x43e, 0x1c82), # оᲂ
66+
# CYRILLIC SMALL LETTER ES, CYRILLIC SMALL LETTER WIDE ES
67+
(0x441, 0x1c83), # сᲃ
68+
# CYRILLIC SMALL LETTER TE, CYRILLIC SMALL LETTER TALL TE, CYRILLIC SMALL LETTER THREE-LEGGED TE
69+
(0x442, 0x1c84, 0x1c85), # тᲄᲅ
70+
# CYRILLIC SMALL LETTER HARD SIGN, CYRILLIC SMALL LETTER TALL HARD SIGN
71+
(0x44a, 0x1c86), # ъᲆ
72+
# CYRILLIC SMALL LETTER YAT, CYRILLIC SMALL LETTER TALL YAT
73+
(0x463, 0x1c87), # ѣᲇ
74+
# CYRILLIC SMALL LETTER UNBLENDED UK, CYRILLIC SMALL LETTER MONOGRAPH UK
75+
(0x1c88, 0xa64b), # ᲈꙋ
6076
# LATIN SMALL LETTER S WITH DOT ABOVE, LATIN SMALL LETTER LONG S WITH DOT ABOVE
6177
(0x1e61, 0x1e9b), # ṡẛ
6278
# LATIN SMALL LIGATURE LONG S T, LATIN SMALL LIGATURE ST
@@ -339,11 +355,19 @@ def _optimize_charset(charset, iscased=None, fixup=None, fixes=None):
339355
charmap += b'\0' * 0xff00
340356
continue
341357
# Character set contains non-BMP character codes.
358+
# For range, all BMP characters in the range are already
359+
# proceeded.
342360
if fixup:
343361
hascased = True
344-
# There are only two ranges of cased non-BMP characters:
345-
# 10400-1044F (Deseret) and 118A0-118DF (Warang Citi),
346-
# and for both ranges RANGE_UNI_IGNORE works.
362+
# For now, IN_UNI_IGNORE+LITERAL and
363+
# IN_UNI_IGNORE+RANGE_UNI_IGNORE work for all non-BMP
364+
# characters, because two characters (at least one of
365+
# which is not in the BMP) match case-insensitively
366+
# if and only if:
367+
# 1) c1.lower() == c2.lower()
368+
# 2) c1.lower() == c2 or c1.lower().upper() == c2
369+
# Also, both c.lower() and c.lower().upper() are single
370+
# characters for every non-BMP character.
347371
if op is RANGE:
348372
op = RANGE_UNI_IGNORE
349373
tail.append((op, av))

Diff for: Lib/test/test_re.py

+49-6
Original file line numberDiff line numberDiff line change
@@ -859,16 +859,30 @@ def test_ignore_case(self):
859859
self.assertEqual(re.match(r"((a)\s(abc|a))", "a a", re.I).group(1), "a a")
860860
self.assertEqual(re.match(r"((a)\s(abc|a)*)", "a aa", re.I).group(1), "a aa")
861861

862-
assert '\u212a'.lower() == 'k' # 'K'
862+
# Two different characters have the same lowercase.
863+
assert 'K'.lower() == '\u212a'.lower() == 'k' # 'K'
863864
self.assertTrue(re.match(r'K', '\u212a', re.I))
864865
self.assertTrue(re.match(r'k', '\u212a', re.I))
865866
self.assertTrue(re.match(r'\u212a', 'K', re.I))
866867
self.assertTrue(re.match(r'\u212a', 'k', re.I))
867-
assert '\u017f'.upper() == 'S' # 'ſ'
868+
869+
# Two different characters have the same uppercase.
870+
assert 's'.upper() == '\u017f'.upper() == 'S' # 'ſ'
868871
self.assertTrue(re.match(r'S', '\u017f', re.I))
869872
self.assertTrue(re.match(r's', '\u017f', re.I))
870873
self.assertTrue(re.match(r'\u017f', 'S', re.I))
871874
self.assertTrue(re.match(r'\u017f', 's', re.I))
875+
876+
# Two different characters have the same uppercase. Unicode 9.0+.
877+
assert '\u0432'.upper() == '\u1c80'.upper() == '\u0412' # 'в', 'ᲀ', 'В'
878+
self.assertTrue(re.match(r'\u0412', '\u0432', re.I))
879+
self.assertTrue(re.match(r'\u0412', '\u1c80', re.I))
880+
self.assertTrue(re.match(r'\u0432', '\u0412', re.I))
881+
self.assertTrue(re.match(r'\u0432', '\u1c80', re.I))
882+
self.assertTrue(re.match(r'\u1c80', '\u0412', re.I))
883+
self.assertTrue(re.match(r'\u1c80', '\u0432', re.I))
884+
885+
# Two different characters have the same multicharacter uppercase.
872886
assert '\ufb05'.upper() == '\ufb06'.upper() == 'ST' # 'ſt', 'st'
873887
self.assertTrue(re.match(r'\ufb05', '\ufb06', re.I))
874888
self.assertTrue(re.match(r'\ufb06', '\ufb05', re.I))
@@ -882,16 +896,31 @@ def test_ignore_case_set(self):
882896
self.assertTrue(re.match(br'[19a]', b'a', re.I))
883897
self.assertTrue(re.match(br'[19a]', b'A', re.I))
884898
self.assertTrue(re.match(br'[19A]', b'a', re.I))
885-
assert '\u212a'.lower() == 'k' # 'K'
899+
900+
# Two different characters have the same lowercase.
901+
assert 'K'.lower() == '\u212a'.lower() == 'k' # 'K'
886902
self.assertTrue(re.match(r'[19K]', '\u212a', re.I))
887903
self.assertTrue(re.match(r'[19k]', '\u212a', re.I))
888904
self.assertTrue(re.match(r'[19\u212a]', 'K', re.I))
889905
self.assertTrue(re.match(r'[19\u212a]', 'k', re.I))
890-
assert '\u017f'.upper() == 'S' # 'ſ'
906+
907+
# Two different characters have the same uppercase.
908+
assert 's'.upper() == '\u017f'.upper() == 'S' # 'ſ'
891909
self.assertTrue(re.match(r'[19S]', '\u017f', re.I))
892910
self.assertTrue(re.match(r'[19s]', '\u017f', re.I))
893911
self.assertTrue(re.match(r'[19\u017f]', 'S', re.I))
894912
self.assertTrue(re.match(r'[19\u017f]', 's', re.I))
913+
914+
# Two different characters have the same uppercase. Unicode 9.0+.
915+
assert '\u0432'.upper() == '\u1c80'.upper() == '\u0412' # 'в', 'ᲀ', 'В'
916+
self.assertTrue(re.match(r'[19\u0412]', '\u0432', re.I))
917+
self.assertTrue(re.match(r'[19\u0412]', '\u1c80', re.I))
918+
self.assertTrue(re.match(r'[19\u0432]', '\u0412', re.I))
919+
self.assertTrue(re.match(r'[19\u0432]', '\u1c80', re.I))
920+
self.assertTrue(re.match(r'[19\u1c80]', '\u0412', re.I))
921+
self.assertTrue(re.match(r'[19\u1c80]', '\u0432', re.I))
922+
923+
# Two different characters have the same multicharacter uppercase.
895924
assert '\ufb05'.upper() == '\ufb06'.upper() == 'ST' # 'ſt', 'st'
896925
self.assertTrue(re.match(r'[19\ufb05]', '\ufb06', re.I))
897926
self.assertTrue(re.match(r'[19\ufb06]', '\ufb05', re.I))
@@ -915,16 +944,30 @@ def test_ignore_case_range(self):
915944
self.assertTrue(re.match(r'[\U00010400-\U00010427]', '\U00010428', re.I))
916945
self.assertTrue(re.match(r'[\U00010400-\U00010427]', '\U00010400', re.I))
917946

918-
assert '\u212a'.lower() == 'k' # 'K'
947+
# Two different characters have the same lowercase.
948+
assert 'K'.lower() == '\u212a'.lower() == 'k' # 'K'
919949
self.assertTrue(re.match(r'[J-M]', '\u212a', re.I))
920950
self.assertTrue(re.match(r'[j-m]', '\u212a', re.I))
921951
self.assertTrue(re.match(r'[\u2129-\u212b]', 'K', re.I))
922952
self.assertTrue(re.match(r'[\u2129-\u212b]', 'k', re.I))
923-
assert '\u017f'.upper() == 'S' # 'ſ'
953+
954+
# Two different characters have the same uppercase.
955+
assert 's'.upper() == '\u017f'.upper() == 'S' # 'ſ'
924956
self.assertTrue(re.match(r'[R-T]', '\u017f', re.I))
925957
self.assertTrue(re.match(r'[r-t]', '\u017f', re.I))
926958
self.assertTrue(re.match(r'[\u017e-\u0180]', 'S', re.I))
927959
self.assertTrue(re.match(r'[\u017e-\u0180]', 's', re.I))
960+
961+
# Two different characters have the same uppercase. Unicode 9.0+.
962+
assert '\u0432'.upper() == '\u1c80'.upper() == '\u0412' # 'в', 'ᲀ', 'В'
963+
self.assertTrue(re.match(r'[\u0411-\u0413]', '\u0432', re.I))
964+
self.assertTrue(re.match(r'[\u0411-\u0413]', '\u1c80', re.I))
965+
self.assertTrue(re.match(r'[\u0431-\u0433]', '\u0412', re.I))
966+
self.assertTrue(re.match(r'[\u0431-\u0433]', '\u1c80', re.I))
967+
self.assertTrue(re.match(r'[\u1c80-\u1c82]', '\u0412', re.I))
968+
self.assertTrue(re.match(r'[\u1c80-\u1c82]', '\u0432', re.I))
969+
970+
# Two different characters have the same multicharacter uppercase.
928971
assert '\ufb05'.upper() == '\ufb06'.upper() == 'ST' # 'ſt', 'st'
929972
self.assertTrue(re.match(r'[\ufb04-\ufb05]', '\ufb06', re.I))
930973
self.assertTrue(re.match(r'[\ufb06-\ufb07]', '\ufb05', re.I))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Update case-insensitive matching in the :mod:`re` module to the latest
2+
Unicode version.

0 commit comments

Comments
 (0)