Skip to content

Commit d74ed5a

Browse files
committed
[mypyc] Implement bytes.endswith
1 parent 72cff30 commit d74ed5a

File tree

6 files changed

+85
-1
lines changed

6 files changed

+85
-1
lines changed

mypyc/lib-rt/CPy.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,7 @@ PyObject *CPyBytes_Join(PyObject *sep, PyObject *iter);
784784
CPyTagged CPyBytes_Ord(PyObject *obj);
785785
PyObject *CPyBytes_Multiply(PyObject *bytes, CPyTagged count);
786786
int CPyBytes_Startswith(PyObject *self, PyObject *subobj);
787-
787+
int CPyBytes_Endswith(PyObject *self, PyObject *subobj);
788788
int CPyBytes_Compare(PyObject *left, PyObject *right);
789789

790790

mypyc/lib-rt/bytes_ops.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,41 @@ int CPyBytes_Startswith(PyObject *self, PyObject *subobj) {
209209
}
210210
return ret;
211211
}
212+
213+
int CPyBytes_Endswith(PyObject *self, PyObject *subobj) {
214+
if (PyBytes_CheckExact(self) && PyBytes_CheckExact(subobj)) {
215+
if (self == subobj) {
216+
return 1;
217+
}
218+
219+
Py_ssize_t subobj_len = PyBytes_GET_SIZE(subobj);
220+
if (subobj_len == 0) {
221+
return 1;
222+
}
223+
224+
Py_ssize_t self_len = PyBytes_GET_SIZE(self);
225+
if (subobj_len > self_len) {
226+
return 0;
227+
}
228+
229+
const char *self_buf = PyBytes_AS_STRING(self);
230+
const char *subobj_buf = PyBytes_AS_STRING(subobj);
231+
232+
return memcmp(self_buf + (self_len - subobj_len), subobj_buf, (size_t)subobj_len) == 0 ? 1 : 0;
233+
}
234+
_Py_IDENTIFIER(endswith);
235+
PyObject *name = _PyUnicode_FromId(&PyId_endswith);
236+
if (name == NULL) {
237+
return 2;
238+
}
239+
PyObject *result = PyObject_CallMethodOneArg(self, name, subobj);
240+
if (result == NULL) {
241+
return 2;
242+
}
243+
int ret = PyObject_IsTrue(result);
244+
Py_DECREF(result);
245+
if (ret < 0) {
246+
return 2;
247+
}
248+
return ret;
249+
}

mypyc/primitives/bytes_ops.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,16 @@
150150
error_kind=ERR_MAGIC,
151151
)
152152

153+
# bytes.endswith(bytes)
154+
method_op(
155+
name="endswith",
156+
arg_types=[bytes_rprimitive, bytes_rprimitive],
157+
return_type=c_int_rprimitive,
158+
c_function_name="CPyBytes_Endswith",
159+
truncated_type=bool_rprimitive,
160+
error_kind=ERR_MAGIC,
161+
)
162+
153163
# Join bytes objects and return a new bytes.
154164
# The first argument is the total number of the following bytes.
155165
bytes_build_op = custom_op(

mypyc/test-data/fixtures/ir.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ def join(self, x: Iterable[object]) -> bytes: ...
180180
def decode(self, encoding: str=..., errors: str=...) -> str: ...
181181
def translate(self, t: bytes) -> bytes: ...
182182
def startswith(self, t: bytes) -> bool: ...
183+
def endswith(self, t: bytes) -> bool: ...
183184
def __iter__(self) -> Iterator[int]: ...
184185

185186
class bytearray:
@@ -194,6 +195,7 @@ def __setitem__(self, i: int, o: int) -> None: ...
194195
def __getitem__(self, i: int) -> int: ...
195196
def decode(self, x: str = ..., y: str = ...) -> str: ...
196197
def startswith(self, t: bytes) -> bool: ...
198+
def endswith(self, t: bytes) -> bool: ...
197199

198200
class bool(int):
199201
def __init__(self, o: object = ...) -> None: ...

mypyc/test-data/irbuild-bytes.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,16 @@ L0:
261261
r0 = CPyBytes_Startswith(a, b)
262262
r1 = truncate r0: i32 to builtins.bool
263263
return r1
264+
265+
[case testBytesEndsWith]
266+
def f(a: bytes, b: bytes) -> bool:
267+
return a.endswith(b)
268+
[out]
269+
def f(a, b):
270+
a, b :: bytes
271+
r0 :: i32
272+
r1 :: bool
273+
L0:
274+
r0 = CPyBytes_Endswith(a, b)
275+
r1 = truncate r0: i32 to builtins.bool
276+
return r1

mypyc/test-data/run-bytes.test

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,27 @@ def test_startswith() -> None:
221221
assert test.startswith(b'some')
222222
assert not test.startswith(b'other')
223223

224+
def test_endswith() -> None:
225+
# Test default behavior
226+
test = b'some string'
227+
assert test.endswith(b'string')
228+
assert test.endswith(b'some string')
229+
assert not test.endswith(b'other')
230+
assert not test.endswith(b'some string but longer')
231+
232+
# Test empty cases
233+
assert test.endswith(b'')
234+
assert b''.endswith(b'')
235+
assert not b''.endswith(test)
236+
237+
# Test bytearray to verify slow paths
238+
assert test.endswith(bytearray(b'string'))
239+
assert not test.endswith(bytearray(b'other'))
240+
241+
test = bytearray(b'some string')
242+
assert test.endswith(b'string')
243+
assert not test.endswith(b'other')
244+
224245
[case testBytesSlicing]
225246
def test_bytes_slicing() -> None:
226247
b = b'abcdefg'

0 commit comments

Comments
 (0)