Skip to content

Commit 8fc8d26

Browse files
authored
[mypyc] Optimize str.removeprefix and str.removesuffix (#18672)
`str.removeprefix` and `str.removesuffix` were added in Python 3.9.
1 parent 555bfae commit 8fc8d26

File tree

6 files changed

+51
-1
lines changed

6 files changed

+51
-1
lines changed

mypyc/doc/str_operations.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Methods
3232
* ``s.encode(encoding: str, errors: str)``
3333
* ``s1.endswith(s2: str)``
3434
* ``s.join(x: Iterable)``
35+
* ``s.removeprefix(prefix: str)``
36+
* ``s.removesuffix(suffix: str)``
3537
* ``s.replace(old: str, new: str)``
3638
* ``s.replace(old: str, new: str, count: int)``
3739
* ``s.split()``

mypyc/lib-rt/CPy.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,8 @@ PyObject *CPyStr_Append(PyObject *o1, PyObject *o2);
726726
PyObject *CPyStr_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end);
727727
bool CPyStr_Startswith(PyObject *self, PyObject *subobj);
728728
bool CPyStr_Endswith(PyObject *self, PyObject *subobj);
729+
PyObject *CPyStr_Removeprefix(PyObject *self, PyObject *prefix);
730+
PyObject *CPyStr_Removesuffix(PyObject *self, PyObject *suffix);
729731
bool CPyStr_IsTrue(PyObject *obj);
730732
Py_ssize_t CPyStr_Size_size_t(PyObject *str);
731733
PyObject *CPy_Decode(PyObject *obj, PyObject *encoding, PyObject *errors);

mypyc/lib-rt/str_ops.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,26 @@ bool CPyStr_Endswith(PyObject *self, PyObject *subobj) {
164164
return PyUnicode_Tailmatch(self, subobj, start, end, 1);
165165
}
166166

167+
PyObject *CPyStr_Removeprefix(PyObject *self, PyObject *prefix) {
168+
Py_ssize_t end = PyUnicode_GET_LENGTH(self);
169+
int match = PyUnicode_Tailmatch(self, prefix, 0, end, -1);
170+
if (match) {
171+
Py_ssize_t prefix_end = PyUnicode_GET_LENGTH(prefix);
172+
return PyUnicode_Substring(self, prefix_end, end);
173+
}
174+
return Py_NewRef(self);
175+
}
176+
177+
PyObject *CPyStr_Removesuffix(PyObject *self, PyObject *suffix) {
178+
Py_ssize_t end = PyUnicode_GET_LENGTH(self);
179+
int match = PyUnicode_Tailmatch(self, suffix, 0, end, 1);
180+
if (match) {
181+
Py_ssize_t suffix_end = PyUnicode_GET_LENGTH(suffix);
182+
return PyUnicode_Substring(self, 0, end - suffix_end);
183+
}
184+
return Py_NewRef(self);
185+
}
186+
167187
/* This does a dodgy attempt to append in place */
168188
PyObject *CPyStr_Append(PyObject *o1, PyObject *o2) {
169189
PyUnicode_Append(&o1, o2);

mypyc/primitives/str_ops.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,24 @@
118118
error_kind=ERR_NEVER,
119119
)
120120

121+
# str.removeprefix(str)
122+
method_op(
123+
name="removeprefix",
124+
arg_types=[str_rprimitive, str_rprimitive],
125+
return_type=str_rprimitive,
126+
c_function_name="CPyStr_Removeprefix",
127+
error_kind=ERR_NEVER,
128+
)
129+
130+
# str.removesuffix(str)
131+
method_op(
132+
name="removesuffix",
133+
arg_types=[str_rprimitive, str_rprimitive],
134+
return_type=str_rprimitive,
135+
c_function_name="CPyStr_Removesuffix",
136+
error_kind=ERR_NEVER,
137+
)
138+
121139
# str.split(...)
122140
str_split_types: list[RType] = [str_rprimitive, str_rprimitive, int_rprimitive]
123141
str_split_functions = ["PyUnicode_Split", "PyUnicode_Split", "CPyStr_Split"]

mypyc/test-data/fixtures/ir.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ def startswith(self, x: str, start: int=..., end: int=...) -> bool: ...
111111
def endswith(self, x: str, start: int=..., end: int=...) -> bool: ...
112112
def replace(self, old: str, new: str, maxcount: int=...) -> str: ...
113113
def encode(self, encoding: str=..., errors: str=...) -> bytes: ...
114+
def removeprefix(self, prefix: str, /) -> str: ...
115+
def removesuffix(self, suffix: str, /) -> str: ...
114116

115117
class float:
116118
def __init__(self, x: object) -> None: pass

mypyc/test-data/run-strings.test

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ def eq(x: str) -> int:
2020
return 2
2121
def match(x: str, y: str) -> Tuple[bool, bool]:
2222
return (x.startswith(y), x.endswith(y))
23+
def remove_prefix_suffix(x: str, y: str) -> Tuple[str, str]:
24+
return (x.removeprefix(y), x.removesuffix(y))
2325

2426
[file driver.py]
25-
from native import f, g, tostr, booltostr, concat, eq, match
27+
from native import f, g, tostr, booltostr, concat, eq, match, remove_prefix_suffix
2628
import sys
2729

2830
assert f() == 'some string'
@@ -44,6 +46,10 @@ assert match('abc', 'a') == (True, False)
4446
assert match('abc', 'c') == (False, True)
4547
assert match('', 'abc') == (False, False)
4648

49+
assert remove_prefix_suffix('', '') == ('', '')
50+
assert remove_prefix_suffix('abc', 'a') == ('bc', 'abc')
51+
assert remove_prefix_suffix('abc', 'c') == ('abc', 'ab')
52+
4753
[case testStringOps]
4854
from typing import List, Optional
4955

0 commit comments

Comments
 (0)