Skip to content

Commit 64ca418

Browse files
gh-156513: Match a backreference case-insensitively as a literal
sre_lower_unicode() now returns the lowercase of the simple case folding of the character, computed by the new _PyUnicode_ToFoldedLowercase(), with the four pairs which no case mapping unifies hardcoded. The key is the same for all characters matched case-insensitively, so a backreference matches what a literal matches: (?i)(.)\1 matches 'ςσ' as (?i)ς already matched 'σ'. _casefix._EXTRA_CASES is no longer needed and is removed, together with the alternatives which the compiler expanded a literal into. Its generator now fails if sre_lower_unicode() does not fold to a single code a group of characters which have to be matched case-insensitively. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 7a8132e commit 64ca418

10 files changed

Lines changed: 194 additions & 200 deletions

File tree

Include/internal/pycore_unicodectype.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extern int _PyUnicode_ToLowerFull(Py_UCS4 ch, Py_UCS4 *res);
1212
extern int _PyUnicode_ToTitleFull(Py_UCS4 ch, Py_UCS4 *res);
1313
extern int _PyUnicode_ToUpperFull(Py_UCS4 ch, Py_UCS4 *res);
1414
extern int _PyUnicode_ToFoldedFull(Py_UCS4 ch, Py_UCS4 *res);
15+
extern Py_UCS4 _PyUnicode_ToFoldedLowercase(Py_UCS4 ch);
1516
extern int _PyUnicode_IsCaseIgnorable(Py_UCS4 ch);
1617
extern int _PyUnicode_IsCased(Py_UCS4 ch);
1718

Lib/re/_casefix.py

Lines changed: 28 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
# Auto-generated by Tools/build/generate_re_casefix.py.
22

3-
# Maps the code of lowercased character to codes of different lowercased
4-
# characters which have the same uppercase.
5-
_EXTRA_CASES = {
6-
# LATIN SMALL LETTER I: LATIN SMALL LETTER DOTLESS I
7-
0x0069: (0x0131,), # 'i': 'ı'
3+
# Maps the code of a folded character to codes of characters which are
4+
# folded to it (as by _sre.unicode_tolower), except its uppercase (as by
5+
# _sre.unicode_toupper) when that uppercase is folded back to it.
6+
_CASE_PREIMAGE_FIXES = {
7+
# LATIN SMALL LETTER I: LATIN CAPITAL LETTER I WITH DOT ABOVE, LATIN SMALL LETTER DOTLESS I
8+
0x0069: (0x0130, 0x0131), # 'i': 'İı'
9+
# LATIN SMALL LETTER K: KELVIN SIGN
10+
0x006b: (0x212a,), # 'k': 'K'
811
# LATIN SMALL LETTER S: LATIN SMALL LETTER LONG S
912
0x0073: (0x017f,), # 's': 'ſ'
10-
# MICRO SIGN: GREEK SMALL LETTER MU
11-
0x00b5: (0x03bc,), # 'µ': 'μ'
12-
# LATIN SMALL LETTER DOTLESS I: LATIN SMALL LETTER I
13-
0x0131: (0x0069,), # 'ı': 'i'
14-
# LATIN SMALL LETTER LONG S: LATIN SMALL LETTER S
15-
0x017f: (0x0073,), # 'ſ': 's'
16-
# COMBINING GREEK YPOGEGRAMMENI: GREEK SMALL LETTER IOTA, GREEK PROSGEGRAMMENI
17-
0x0345: (0x03b9, 0x1fbe), # '\u0345': 'ιι'
13+
# LATIN SMALL LETTER SHARP S: LATIN CAPITAL LETTER SHARP S
14+
0x00df: (0x1e9e,), # 'ß': 'ẞ'
15+
# LATIN SMALL LETTER A WITH RING ABOVE: ANGSTROM SIGN
16+
0x00e5: (0x212b,), # 'å': 'Å'
17+
# LATIN SMALL LETTER DZ WITH CARON: LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON
18+
0x01c6: (0x01c5,), # 'dž': 'Dž'
19+
# LATIN SMALL LETTER LJ: LATIN CAPITAL LETTER L WITH SMALL LETTER J
20+
0x01c9: (0x01c8,), # 'lj': 'Lj'
21+
# LATIN SMALL LETTER NJ: LATIN CAPITAL LETTER N WITH SMALL LETTER J
22+
0x01cc: (0x01cb,), # 'nj': 'Nj'
23+
# LATIN SMALL LETTER DZ: LATIN CAPITAL LETTER D WITH SMALL LETTER Z
24+
0x01f3: (0x01f2,), # 'dz': 'Dz'
1825
# GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
1926
0x0390: (0x1fd3,), # 'ΐ': 'ΐ'
2027
# GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
@@ -23,8 +30,8 @@
2330
0x03b2: (0x03d0,), # 'β': 'ϐ'
2431
# GREEK SMALL LETTER EPSILON: GREEK LUNATE EPSILON SYMBOL
2532
0x03b5: (0x03f5,), # 'ε': 'ϵ'
26-
# GREEK SMALL LETTER THETA: GREEK THETA SYMBOL
27-
0x03b8: (0x03d1,), # 'θ': 'ϑ'
33+
# GREEK SMALL LETTER THETA: GREEK THETA SYMBOL, GREEK CAPITAL THETA SYMBOL
34+
0x03b8: (0x03d1, 0x03f4), # 'θ': 'ϑϴ'
2835
# GREEK SMALL LETTER IOTA: COMBINING GREEK YPOGEGRAMMENI, GREEK PROSGEGRAMMENI
2936
0x03b9: (0x0345, 0x1fbe), # 'ι': '\u0345ι'
3037
# GREEK SMALL LETTER KAPPA: GREEK KAPPA SYMBOL
@@ -35,26 +42,12 @@
3542
0x03c0: (0x03d6,), # 'π': 'ϖ'
3643
# GREEK SMALL LETTER RHO: GREEK RHO SYMBOL
3744
0x03c1: (0x03f1,), # 'ρ': 'ϱ'
38-
# GREEK SMALL LETTER FINAL SIGMA: GREEK SMALL LETTER SIGMA
39-
0x03c2: (0x03c3,), # 'ς': 'σ'
4045
# GREEK SMALL LETTER SIGMA: GREEK SMALL LETTER FINAL SIGMA
4146
0x03c3: (0x03c2,), # 'σ': 'ς'
4247
# GREEK SMALL LETTER PHI: GREEK PHI SYMBOL
4348
0x03c6: (0x03d5,), # 'φ': 'ϕ'
44-
# GREEK BETA SYMBOL: GREEK SMALL LETTER BETA
45-
0x03d0: (0x03b2,), # 'ϐ': 'β'
46-
# GREEK THETA SYMBOL: GREEK SMALL LETTER THETA
47-
0x03d1: (0x03b8,), # 'ϑ': 'θ'
48-
# GREEK PHI SYMBOL: GREEK SMALL LETTER PHI
49-
0x03d5: (0x03c6,), # 'ϕ': 'φ'
50-
# GREEK PI SYMBOL: GREEK SMALL LETTER PI
51-
0x03d6: (0x03c0,), # 'ϖ': 'π'
52-
# GREEK KAPPA SYMBOL: GREEK SMALL LETTER KAPPA
53-
0x03f0: (0x03ba,), # 'ϰ': 'κ'
54-
# GREEK RHO SYMBOL: GREEK SMALL LETTER RHO
55-
0x03f1: (0x03c1,), # 'ϱ': 'ρ'
56-
# GREEK LUNATE EPSILON SYMBOL: GREEK SMALL LETTER EPSILON
57-
0x03f5: (0x03b5,), # 'ϵ': 'ε'
49+
# GREEK SMALL LETTER OMEGA: OHM SIGN
50+
0x03c9: (0x2126,), # 'ω': 'Ω'
5851
# CYRILLIC SMALL LETTER VE: CYRILLIC SMALL LETTER ROUNDED VE
5952
0x0432: (0x1c80,), # 'в': 'ᲀ'
6053
# CYRILLIC SMALL LETTER DE: CYRILLIC SMALL LETTER LONG-LEGGED DE
@@ -69,66 +62,8 @@
6962
0x044a: (0x1c86,), # 'ъ': 'ᲆ'
7063
# CYRILLIC SMALL LETTER YAT: CYRILLIC SMALL LETTER TALL YAT
7164
0x0463: (0x1c87,), # 'ѣ': 'ᲇ'
72-
# CYRILLIC SMALL LETTER ROUNDED VE: CYRILLIC SMALL LETTER VE
73-
0x1c80: (0x0432,), # 'ᲀ': 'в'
74-
# CYRILLIC SMALL LETTER LONG-LEGGED DE: CYRILLIC SMALL LETTER DE
75-
0x1c81: (0x0434,), # 'ᲁ': 'д'
76-
# CYRILLIC SMALL LETTER NARROW O: CYRILLIC SMALL LETTER O
77-
0x1c82: (0x043e,), # 'ᲂ': 'о'
78-
# CYRILLIC SMALL LETTER WIDE ES: CYRILLIC SMALL LETTER ES
79-
0x1c83: (0x0441,), # 'ᲃ': 'с'
80-
# CYRILLIC SMALL LETTER TALL TE: CYRILLIC SMALL LETTER TE, CYRILLIC SMALL LETTER THREE-LEGGED TE
81-
0x1c84: (0x0442, 0x1c85), # 'ᲄ': 'тᲅ'
82-
# CYRILLIC SMALL LETTER THREE-LEGGED TE: CYRILLIC SMALL LETTER TE, CYRILLIC SMALL LETTER TALL TE
83-
0x1c85: (0x0442, 0x1c84), # 'ᲅ': 'тᲄ'
84-
# CYRILLIC SMALL LETTER TALL HARD SIGN: CYRILLIC SMALL LETTER HARD SIGN
85-
0x1c86: (0x044a,), # 'ᲆ': 'ъ'
86-
# CYRILLIC SMALL LETTER TALL YAT: CYRILLIC SMALL LETTER YAT
87-
0x1c87: (0x0463,), # 'ᲇ': 'ѣ'
88-
# CYRILLIC SMALL LETTER UNBLENDED UK: CYRILLIC SMALL LETTER MONOGRAPH UK
89-
0x1c88: (0xa64b,), # 'ᲈ': 'ꙋ'
9065
# LATIN SMALL LETTER S WITH DOT ABOVE: LATIN SMALL LETTER LONG S WITH DOT ABOVE
9166
0x1e61: (0x1e9b,), # 'ṡ': 'ẛ'
92-
# LATIN SMALL LETTER LONG S WITH DOT ABOVE: LATIN SMALL LETTER S WITH DOT ABOVE
93-
0x1e9b: (0x1e61,), # 'ẛ': 'ṡ'
94-
# GREEK PROSGEGRAMMENI: COMBINING GREEK YPOGEGRAMMENI, GREEK SMALL LETTER IOTA
95-
0x1fbe: (0x0345, 0x03b9), # 'ι': '\u0345ι'
96-
# GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
97-
0x1fd3: (0x0390,), # 'ΐ': 'ΐ'
98-
# GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
99-
0x1fe3: (0x03b0,), # 'ΰ': 'ΰ'
100-
# CYRILLIC SMALL LETTER MONOGRAPH UK: CYRILLIC SMALL LETTER UNBLENDED UK
101-
0xa64b: (0x1c88,), # 'ꙋ': 'ᲈ'
102-
# LATIN SMALL LIGATURE LONG S T: LATIN SMALL LIGATURE ST
103-
0xfb05: (0xfb06,), # 'ſt': 'st'
104-
# LATIN SMALL LIGATURE ST: LATIN SMALL LIGATURE LONG S T
105-
0xfb06: (0xfb05,), # 'st': 'ſt'
106-
}
107-
108-
# Maps the code of a lowercased character to codes of characters which
109-
# are lowercased to it (as by _sre.unicode_tolower), except its uppercase
110-
# (as by _sre.unicode_toupper) when that uppercase is lowercased back to it.
111-
_CASE_PREIMAGE_FIXES = {
112-
# LATIN SMALL LETTER I: LATIN CAPITAL LETTER I WITH DOT ABOVE
113-
0x0069: (0x0130,), # 'i': 'İ'
114-
# LATIN SMALL LETTER K: KELVIN SIGN
115-
0x006b: (0x212a,), # 'k': 'K'
116-
# LATIN SMALL LETTER SHARP S: LATIN CAPITAL LETTER SHARP S
117-
0x00df: (0x1e9e,), # 'ß': 'ẞ'
118-
# LATIN SMALL LETTER A WITH RING ABOVE: ANGSTROM SIGN
119-
0x00e5: (0x212b,), # 'å': 'Å'
120-
# LATIN SMALL LETTER DZ WITH CARON: LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON
121-
0x01c6: (0x01c5,), # 'dž': 'Dž'
122-
# LATIN SMALL LETTER LJ: LATIN CAPITAL LETTER L WITH SMALL LETTER J
123-
0x01c9: (0x01c8,), # 'lj': 'Lj'
124-
# LATIN SMALL LETTER NJ: LATIN CAPITAL LETTER N WITH SMALL LETTER J
125-
0x01cc: (0x01cb,), # 'nj': 'Nj'
126-
# LATIN SMALL LETTER DZ: LATIN CAPITAL LETTER D WITH SMALL LETTER Z
127-
0x01f3: (0x01f2,), # 'dz': 'Dz'
128-
# GREEK SMALL LETTER THETA: GREEK CAPITAL THETA SYMBOL
129-
0x03b8: (0x03f4,), # 'θ': 'ϴ'
130-
# GREEK SMALL LETTER OMEGA: OHM SIGN
131-
0x03c9: (0x2126,), # 'ω': 'Ω'
13267
# GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI: GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
13368
0x1f80: (0x1f88,), # 'ᾀ': 'ᾈ'
13469
# GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI: GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
@@ -183,4 +118,8 @@
183118
0x1fc3: (0x1fcc,), # 'ῃ': 'ῌ'
184119
# GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI: GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
185120
0x1ff3: (0x1ffc,), # 'ῳ': 'ῼ'
121+
# CYRILLIC SMALL LETTER MONOGRAPH UK: CYRILLIC SMALL LETTER UNBLENDED UK
122+
0xa64b: (0x1c88,), # 'ꙋ': 'ᲈ'
123+
# LATIN SMALL LIGATURE ST: LATIN SMALL LIGATURE LONG S T
124+
0xfb06: (0xfb05,), # 'st': 'ſt'
186125
}

Lib/re/_compiler.py

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import _sre
1414
from . import _parser
1515
from ._constants import *
16-
from ._casefix import _EXTRA_CASES
1716
from ._optimizer import (
1817
_combine_flags, _compile_charset, _optimize_charset, _compile_info,
1918
_simple, _CHARSET_ALL, _CODEBITS, MAXCODE, optimize,
@@ -41,12 +40,12 @@ def _compile(code, pattern, flags):
4140
ASSERT_CODES = _ASSERT_CODES
4241
iscased = None
4342
tolower = None
44-
fixes = None
43+
isunicode = False
4544
if flags & SRE_FLAG_IGNORECASE and not flags & SRE_FLAG_LOCALE:
4645
if flags & SRE_FLAG_UNICODE:
4746
iscased = _sre.unicode_iscased
4847
tolower = _sre.unicode_tolower
49-
fixes = _EXTRA_CASES
48+
isunicode = True
5049
else:
5150
iscased = _sre.ascii_iscased
5251
tolower = _sre.ascii_tolower
@@ -62,25 +61,10 @@ def _compile(code, pattern, flags):
6261
emit(op)
6362
emit(av)
6463
else:
65-
lo = tolower(av)
66-
if not fixes: # ascii
67-
emit(OP_IGNORE[op])
68-
emit(lo)
69-
elif lo not in fixes:
70-
emit(OP_UNICODE_IGNORE[op])
71-
emit(lo)
72-
else:
73-
emit(IN_UNI_IGNORE)
74-
skip = _len(code); emit(0)
75-
if op is NOT_LITERAL:
76-
emit(NEGATE)
77-
for k in (lo,) + fixes[lo]:
78-
emit(LITERAL)
79-
emit(k)
80-
emit(FAILURE)
81-
code[skip] = _len(code) - skip
64+
emit(OP_UNICODE_IGNORE[op] if isunicode else OP_IGNORE[op])
65+
emit(tolower(av))
8266
elif op is IN:
83-
charset, hascased = _optimize_charset(av, iscased, tolower, fixes)
67+
charset, hascased = _optimize_charset(av, iscased, tolower, isunicode)
8468
if not charset:
8569
emit(FAILURE)
8670
elif charset == _CHARSET_ALL:
@@ -90,7 +74,7 @@ def _compile(code, pattern, flags):
9074
emit(IN_LOC_IGNORE)
9175
elif not hascased:
9276
emit(IN)
93-
elif not fixes: # ascii
77+
elif not isunicode:
9478
emit(IN_IGNORE)
9579
else:
9680
emit(IN_UNI_IGNORE)
@@ -192,7 +176,7 @@ def _compile(code, pattern, flags):
192176
emit(op)
193177
elif flags & SRE_FLAG_LOCALE:
194178
emit(GROUPREF_LOC_IGNORE)
195-
elif not fixes: # ascii
179+
elif not isunicode:
196180
emit(GROUPREF_IGNORE)
197181
else:
198182
emit(GROUPREF_UNI_IGNORE)

Lib/re/_optimizer.py

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@
2020
from . import _parser
2121
from ._constants import *
2222

23-
def _case_preimages(x, iscased, fixup, fixes):
24-
# Characters which fixup() lowercases to x: its uppercase, when it is
25-
# lowercased back to x, and rare additional characters (Unicode only).
23+
def _case_preimages(x, iscased, fixup, isunicode):
24+
# Characters which fixup() folds to x: its uppercase, when it is folded
25+
# back to x, and rare additional characters (Unicode only).
2626
if not iscased(x):
2727
return ()
2828
u = _sre.unicode_toupper(x)
2929
if u != x and fixup(u) == x:
30-
return (u, *_CASE_PREIMAGE_FIXES.get(x, ())) if fixes else (u,)
31-
return _CASE_PREIMAGE_FIXES.get(x, ()) if fixes else ()
30+
return (u, *_CASE_PREIMAGE_FIXES.get(x, ())) if isunicode else (u,)
31+
return _CASE_PREIMAGE_FIXES.get(x, ()) if isunicode else ()
3232

3333
_CHARSET_ALL = [(NEGATE, None)]
3434
_UNIT_CODES = {LITERAL, NOT_LITERAL, ANY, IN, CATEGORY}
@@ -66,7 +66,7 @@ def _compile_charset(charset, flags, code):
6666
raise PatternError(f"internal: unsupported set operator {op!r}")
6767
emit(FAILURE)
6868

69-
def _optimize_charset(charset, iscased=None, fixup=None, fixes=None):
69+
def _optimize_charset(charset, iscased=None, fixup=None, isunicode=False):
7070
# internal: optimize character set.
7171
#
7272
# The engine's charset() walk toggles polarity on every NEGATE (see
@@ -78,7 +78,7 @@ def _optimize_charset(charset, iscased=None, fixup=None, fixes=None):
7878
if not negates or negates == [0]:
7979
# Fast path: a plain union, optionally complemented as a whole -- every
8080
# charset the parser produces today, optimized as before.
81-
return _optimize_charset_segment(charset, iscased, fixup, fixes)
81+
return _optimize_charset_segment(charset, iscased, fixup, isunicode)
8282

8383
# Optimize each NEGATE-delimited run on its own. _allow_anyall is off: the
8484
# [\s\S] -> ANY_ALL / [^\s\S] -> empty shortcuts rewrite a whole set and
@@ -89,16 +89,17 @@ def _optimize_charset(charset, iscased=None, fixup=None, fixes=None):
8989
for i in negates + [len(charset)]:
9090
if i > start: # skip an empty run (e.g. a leading NEGATE)
9191
opt, cased = _optimize_charset_segment(
92-
charset[start:i], iscased, fixup, fixes, _allow_anyall=False)
92+
charset[start:i], iscased, fixup, isunicode,
93+
_allow_anyall=False)
9394
out.extend(opt)
9495
hascased |= cased
9596
if i < len(charset):
9697
out.append((NEGATE, None))
9798
start = i + 1
9899
return out, hascased
99100

100-
def _optimize_charset_segment(charset, iscased=None, fixup=None, fixes=None,
101-
_allow_anyall=True):
101+
def _optimize_charset_segment(charset, iscased=None, fixup=None,
102+
isunicode=False, _allow_anyall=True):
102103
# internal: optimize one NEGATE-free union of character-set members
103104
out = []
104105
tail = []
@@ -107,7 +108,6 @@ def _optimize_charset_segment(charset, iscased=None, fixup=None, fixes=None,
107108
expanded = False # members were case-expanded; the original set no
108109
# longer represents the charmap
109110
wide = set() # case-expansion characters above the charmap size
110-
extras = fixes or {}
111111
for op, av in charset:
112112
while True:
113113
try:
@@ -116,29 +116,27 @@ def _optimize_charset_segment(charset, iscased=None, fixup=None, fixes=None,
116116
av0 = av
117117
av = fixup(av)
118118
charmap[av] = 1
119-
for x in (av, *extras.get(av, ())):
120-
for v in (x, *_case_preimages(x, iscased,
121-
fixup, fixes)):
122-
expanded |= v != av0
123-
if v < len(charmap):
124-
charmap[v] = 1
125-
else:
126-
wide.add(v)
119+
for v in (av, *_case_preimages(av, iscased,
120+
fixup, isunicode)):
121+
expanded |= v != av0
122+
if v < len(charmap):
123+
charmap[v] = 1
124+
else:
125+
wide.add(v)
127126
else:
128127
charmap[av] = 1
129128
elif op is RANGE:
130129
r = range(av[0], av[1]+1)
131130
if fixup: # IGNORECASE and not LOCALE
132131
for i in map(fixup, r):
133132
charmap[i] = 1
134-
for x in (i, *extras.get(i, ())):
135-
for v in (x, *_case_preimages(x, iscased,
136-
fixup, fixes)):
137-
expanded |= not av[0] <= v <= av[1]
138-
if v < len(charmap):
139-
charmap[v] = 1
140-
else:
141-
wide.add(v)
133+
for v in (i, *_case_preimages(i, iscased,
134+
fixup, isunicode)):
135+
expanded |= not av[0] <= v <= av[1]
136+
if v < len(charmap):
137+
charmap[v] = 1
138+
else:
139+
wide.add(v)
142140
else:
143141
for i in r:
144142
charmap[i] = 1
@@ -167,9 +165,11 @@ def _optimize_charset_segment(charset, iscased=None, fixup=None, fixes=None,
167165
# 1) c1.lower() == c2.lower()
168166
# 2) c1.lower() == c2 or c1.lower().upper() == c2
169167
# Also, both c.lower() and c.lower().upper() are single
170-
# characters for every non-BMP character.
168+
# characters for every non-BMP character, and no non-BMP
169+
# character is case folded to a different character than
170+
# it is lowercased to.
171171
if op is RANGE:
172-
if fixes: # not ASCII
172+
if isunicode:
173173
op = RANGE_UNI_IGNORE
174174
hascased = True
175175
else:

0 commit comments

Comments
 (0)