Skip to content

Commit d25cb2b

Browse files
committed
[mypyc] Optimize str.startswith and str.endswith with tuple argument
1 parent 8104d01 commit d25cb2b

File tree

6 files changed

+70
-7
lines changed

6 files changed

+70
-7
lines changed

mypyc/doc/str_operations.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Methods
3131
* ``s.encode(encoding: str)``
3232
* ``s.encode(encoding: str, errors: str)``
3333
* ``s1.endswith(s2: str)``
34+
* ``s1.endswith(t: tuple[str, ...])``
3435
* ``s.join(x: Iterable)``
3536
* ``s.removeprefix(prefix: str)``
3637
* ``s.removesuffix(suffix: str)``
@@ -43,6 +44,7 @@ Methods
4344
* ``s.split(sep: str)``
4445
* ``s.split(sep: str, maxsplit: int)``
4546
* ``s1.startswith(s2: str)``
47+
* ``s1.startswith(t: tuple[str, ...])``
4648

4749
.. note::
4850

mypyc/lib-rt/CPy.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,8 +725,8 @@ PyObject *CPyStr_RSplit(PyObject *str, PyObject *sep, CPyTagged max_split);
725725
PyObject *CPyStr_Replace(PyObject *str, PyObject *old_substr, PyObject *new_substr, CPyTagged max_replace);
726726
PyObject *CPyStr_Append(PyObject *o1, PyObject *o2);
727727
PyObject *CPyStr_GetSlice(PyObject *obj, CPyTagged start, CPyTagged end);
728-
bool CPyStr_Startswith(PyObject *self, PyObject *subobj);
729-
bool CPyStr_Endswith(PyObject *self, PyObject *subobj);
728+
int CPyStr_Startswith(PyObject *self, PyObject *subobj);
729+
int CPyStr_Endswith(PyObject *self, PyObject *subobj);
730730
PyObject *CPyStr_Removeprefix(PyObject *self, PyObject *prefix);
731731
PyObject *CPyStr_Removesuffix(PyObject *self, PyObject *suffix);
732732
bool CPyStr_IsTrue(PyObject *obj);

mypyc/lib-rt/str_ops.c

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,51 @@ PyObject *CPyStr_Replace(PyObject *str, PyObject *old_substr,
161161
return PyUnicode_Replace(str, old_substr, new_substr, temp_max_replace);
162162
}
163163

164-
bool CPyStr_Startswith(PyObject *self, PyObject *subobj) {
164+
int CPyStr_Startswith(PyObject *self, PyObject *subobj) {
165165
Py_ssize_t start = 0;
166166
Py_ssize_t end = PyUnicode_GET_LENGTH(self);
167+
if (PyTuple_Check(subobj)) {
168+
Py_ssize_t i;
169+
for (i = 0; i < PyTuple_GET_SIZE(subobj); i++) {
170+
PyObject *substring = PyTuple_GET_ITEM(subobj, i);
171+
if (!PyUnicode_Check(substring)) {
172+
PyErr_Format(PyExc_TypeError,
173+
"tuple for startswith must only contain str, "
174+
"not %.100s",
175+
Py_TYPE(substring)->tp_name);
176+
return -1;
177+
}
178+
int result = PyUnicode_Tailmatch(self, substring, start, end, -1);
179+
if (result) {
180+
return 1;
181+
}
182+
}
183+
return 0;
184+
}
167185
return PyUnicode_Tailmatch(self, subobj, start, end, -1);
168186
}
169187

170-
bool CPyStr_Endswith(PyObject *self, PyObject *subobj) {
188+
int CPyStr_Endswith(PyObject *self, PyObject *subobj) {
171189
Py_ssize_t start = 0;
172190
Py_ssize_t end = PyUnicode_GET_LENGTH(self);
191+
if (PyTuple_Check(subobj)) {
192+
Py_ssize_t i;
193+
for (i = 0; i < PyTuple_GET_SIZE(subobj); i++) {
194+
PyObject *substring = PyTuple_GET_ITEM(subobj, i);
195+
if (!PyUnicode_Check(substring)) {
196+
PyErr_Format(PyExc_TypeError,
197+
"tuple for endswith must only contain str, "
198+
"not %.100s",
199+
Py_TYPE(substring)->tp_name);
200+
return -1;
201+
}
202+
int result = PyUnicode_Tailmatch(self, substring, start, end, 1);
203+
if (result) {
204+
return 1;
205+
}
206+
}
207+
return 0;
208+
}
173209
return PyUnicode_Tailmatch(self, subobj, start, end, 1);
174210
}
175211

mypyc/primitives/str_ops.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
object_rprimitive,
1616
pointer_rprimitive,
1717
str_rprimitive,
18+
tuple_rprimitive,
1819
)
1920
from mypyc.primitives.registry import (
2021
ERR_NEG_INT,
@@ -109,6 +110,15 @@
109110
error_kind=ERR_NEVER,
110111
)
111112

113+
# str.startswith(tuple) (return -1/0/1)
114+
method_op(
115+
name="startswith",
116+
arg_types=[str_rprimitive, tuple_rprimitive],
117+
return_type=bool_rprimitive,
118+
c_function_name="CPyStr_Startswith",
119+
error_kind=ERR_NEG_INT,
120+
)
121+
112122
# str.endswith(str)
113123
method_op(
114124
name="endswith",
@@ -118,6 +128,15 @@
118128
error_kind=ERR_NEVER,
119129
)
120130

131+
# str.endswith(tuple) (return -1/0/1)
132+
method_op(
133+
name="endswith",
134+
arg_types=[str_rprimitive, tuple_rprimitive],
135+
return_type=bool_rprimitive,
136+
c_function_name="CPyStr_Endswith",
137+
error_kind=ERR_NEG_INT,
138+
)
139+
121140
# str.removeprefix(str)
122141
method_op(
123142
name="removeprefix",

mypyc/test-data/fixtures/ir.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ def strip (self, item: str) -> str: pass
108108
def join(self, x: Iterable[str]) -> str: pass
109109
def format(self, *args: Any, **kwargs: Any) -> str: ...
110110
def upper(self) -> str: ...
111-
def startswith(self, x: str, start: int=..., end: int=...) -> bool: ...
112-
def endswith(self, x: str, start: int=..., end: int=...) -> bool: ...
111+
def startswith(self, x: Union[str, Tuple[str, ...]], start: int=..., end: int=...) -> bool: ...
112+
def endswith(self, x: Union[str, Tuple[str, ...]], start: int=..., end: int=...) -> bool: ...
113113
def replace(self, old: str, new: str, maxcount: int=...) -> str: ...
114114
def encode(self, encoding: str=..., errors: str=...) -> bytes: ...
115115
def removeprefix(self, prefix: str, /) -> str: ...

mypyc/test-data/run-strings.test

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ 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 match_tuple(x: str, y: Tuple[str, ...]) -> Tuple[bool, bool]:
24+
return (x.startswith(y), x.endswith(y))
2325
def remove_prefix_suffix(x: str, y: str) -> Tuple[str, str]:
2426
return (x.removeprefix(y), x.removesuffix(y))
2527

2628
[file driver.py]
27-
from native import f, g, tostr, booltostr, concat, eq, match, remove_prefix_suffix
29+
from native import f, g, tostr, booltostr, concat, eq, match, match_tuple, remove_prefix_suffix
2830
import sys
2931

3032
assert f() == 'some string'
@@ -45,6 +47,10 @@ assert match('abc', '') == (True, True)
4547
assert match('abc', 'a') == (True, False)
4648
assert match('abc', 'c') == (False, True)
4749
assert match('', 'abc') == (False, False)
50+
assert match_tuple('abc', ('d', 'e')) == (False, False)
51+
assert match_tuple('abc', ('a', 'c')) == (True, True)
52+
assert match_tuple('abc', ('a',)) == (True, False)
53+
assert match_tuple('abc', ('c',)) == (False, True)
4854

4955
assert remove_prefix_suffix('', '') == ('', '')
5056
assert remove_prefix_suffix('abc', 'a') == ('bc', 'abc')

0 commit comments

Comments
 (0)