Skip to content

Commit 2058f81

Browse files
committed
[mypyc] feat: new primitive for int.to_bytes
1 parent 3fcfcb8 commit 2058f81

File tree

4 files changed

+91
-1
lines changed

4 files changed

+91
-1
lines changed

mypyc/lib-rt/int_ops.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,3 +581,54 @@ double CPyTagged_TrueDivide(CPyTagged x, CPyTagged y) {
581581
}
582582
return 1.0;
583583
}
584+
585+
// int.to_bytes(length, byteorder, signed=False)
586+
PyObject *CPyInt_ToBytes(CPyTagged self, Py_ssize_t length, PyObject *byteorder, int signed_flag) {
587+
PyObject *pyint = CPyTagged_StealAsObject(self);
588+
if (!PyLong_Check(pyint)) {
589+
Py_DECREF(pyint);
590+
PyErr_SetString(PyExc_TypeError, "self must be int");
591+
return NULL;
592+
}
593+
if (!PyUnicode_Check(byteorder)) {
594+
Py_DECREF(pyint);
595+
PyErr_SetString(PyExc_TypeError, "byteorder must be str");
596+
return NULL;
597+
}
598+
const char *order = PyUnicode_AsUTF8(byteorder);
599+
if (!order) {
600+
Py_DECREF(pyint);
601+
return NULL;
602+
}
603+
PyObject *result = PyLong_ToBytes(pyint, length, order, signed_flag);
604+
Py_DECREF(pyint);
605+
return result;
606+
}
607+
608+
// Helper for PyLong_ToBytes (Python 3.2+)
609+
PyObject *PyLong_ToBytes(PyObject *v, Py_ssize_t length, const char *byteorder, int signed_flag) {
610+
// This is a wrapper for PyLong_AsByteArray and PyBytes_FromStringAndSize
611+
unsigned char *bytes = (unsigned char *)PyMem_Malloc(length);
612+
if (!bytes) {
613+
PyErr_NoMemory();
614+
return NULL;
615+
}
616+
int little_endian = 0;
617+
if (strcmp(byteorder, "little") == 0) {
618+
little_endian = 1;
619+
} else if (strcmp(byteorder, "big") == 0) {
620+
little_endian = 0;
621+
} else {
622+
PyMem_Free(bytes);
623+
PyErr_SetString(PyExc_ValueError, "byteorder must be either 'little' or 'big'");
624+
return NULL;
625+
}
626+
int res = PyLong_AsByteArray((PyLongObject *)v, bytes, length, little_endian, signed_flag);
627+
if (res < 0) {
628+
PyMem_Free(bytes);
629+
return NULL;
630+
}
631+
PyObject *result = PyBytes_FromStringAndSize((const char *)bytes, length);
632+
PyMem_Free(bytes);
633+
return result;
634+
}

mypyc/primitives/int_ops.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
RType,
2222
bit_rprimitive,
2323
bool_rprimitive,
24+
bytes_rprimitive,
2425
c_pyssize_t_rprimitive,
2526
float_rprimitive,
2627
int16_rprimitive,
@@ -31,7 +32,7 @@
3132
str_rprimitive,
3233
void_rtype,
3334
)
34-
from mypyc.primitives.registry import binary_op, custom_op, function_op, load_address_op, unary_op
35+
from mypyc.primitives.registry import binary_op, custom_op, function_op, load_address_op, method_op, unary_op
3536

3637
# Constructors for builtins.int and native int types have the same behavior. In
3738
# interpreted mode, native int types are just aliases to 'int'.
@@ -305,3 +306,21 @@ def int_unary_op(name: str, c_function_name: str) -> PrimitiveDescription:
305306
c_function_name="PyLong_Check",
306307
error_kind=ERR_NEVER,
307308
)
309+
310+
# int.to_bytes(length, byteorder, *, signed=False)
311+
method_op(
312+
name="to_bytes",
313+
arg_types=[int_rprimitive, int_rprimitive, str_rprimitive, bool_rprimitive],
314+
return_type=bytes_rprimitive,
315+
c_function_name="CPyInt_ToBytes",
316+
error_kind=ERR_MAGIC,
317+
)
318+
319+
# int.bit_length()
320+
method_op(
321+
name="bit_length",
322+
arg_types=[int_rprimitive],
323+
return_type=int_rprimitive,
324+
c_function_name="CPyInt_BitLength",
325+
error_kind=ERR_MAGIC,
326+
)

mypyc/test-data/irbuild-int.test

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,14 @@ L0:
210210
r0 = CPyTagged_Invert(n)
211211
x = r0
212212
return x
213+
214+
[case testIntToBytes]
215+
def f(x: int) -> bytes:
216+
return x.to_bytes(2, "big")
217+
[out]
218+
def f(x):
219+
x :: int
220+
r0 :: bytes
221+
L0:
222+
r0 = int_to_bytes x, 2, 'big', 0
223+
return r0

mypyc/test-data/run-integers.test

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,3 +572,12 @@ class subc(int):
572572
[file userdefinedint.py]
573573
class int:
574574
pass
575+
576+
[case testIntToBytes]
577+
def to_bytes(n: int, length: int, byteorder: str, signed: bool = False) -> bytes:
578+
return n.to_bytes(length, byteorder, signed=signed)
579+
def test_to_bytes() -> None:
580+
assert to_bytes(255, 2, "big") == b'\x00\xff'
581+
assert to_bytes(255, 2, "little") == b'\xff\x00'
582+
assert to_bytes(-1, 2, "big", True) == b'\xff\xff'
583+
assert to_bytes(0, 1, "big") == b'\x00'

0 commit comments

Comments
 (0)