diff --git a/mypy/typeshed/stubs/librt/librt/strings.pyi b/mypy/typeshed/stubs/librt/librt/strings.pyi new file mode 100644 index 000000000000..1ee62131257c --- /dev/null +++ b/mypy/typeshed/stubs/librt/librt/strings.pyi @@ -0,0 +1,7 @@ +from typing import final + +@final +class BytesWriter: + def append(self, /, x: int) -> None: ... + def write(self, /, b: bytes) -> None: ... + def getvalue(self) -> bytes: ... diff --git a/mypyc/build.py b/mypyc/build.py index 02f427c83426..848892da669a 100644 --- a/mypyc/build.py +++ b/mypyc/build.py @@ -52,6 +52,7 @@ class ModDesc(NamedTuple): LIBRT_MODULES = [ ModDesc("librt.internal", ["librt_internal.c"], [], []), + ModDesc("librt.strings", ["librt_strings.c"], [], []), ModDesc( "librt.base64", [ diff --git a/mypyc/codegen/emitmodule.py b/mypyc/codegen/emitmodule.py index 8dd9f750a920..be7a24cadbdd 100644 --- a/mypyc/codegen/emitmodule.py +++ b/mypyc/codegen/emitmodule.py @@ -611,6 +611,8 @@ def generate_c_for_modules(self) -> list[tuple[str, str]]: ext_declarations.emit_line("#include ") if any("librt.base64" in mod.capsules for mod in self.modules.values()): ext_declarations.emit_line("#include ") + if any("librt.strings" in mod.capsules for mod in self.modules.values()): + ext_declarations.emit_line("#include ") declarations = Emitter(self.context) declarations.emit_line(f"#ifndef MYPYC_LIBRT_INTERNAL{self.group_suffix}_H") @@ -1045,6 +1047,10 @@ def emit_module_exec_func( emitter.emit_line("if (import_librt_base64() < 0) {") emitter.emit_line("return -1;") emitter.emit_line("}") + if "librt.strings" in module.capsules: + emitter.emit_line("if (import_librt_strings() < 0) {") + emitter.emit_line("return -1;") + emitter.emit_line("}") emitter.emit_line("PyObject* modname = NULL;") if self.multi_phase_init: emitter.emit_line(f"{module_static} = module;") diff --git a/mypyc/ir/rtypes.py b/mypyc/ir/rtypes.py index 66b98e5d6398..2c2fe9da6b04 100644 --- a/mypyc/ir/rtypes.py +++ b/mypyc/ir/rtypes.py @@ -514,7 +514,11 @@ def __hash__(self) -> int: KNOWN_NATIVE_TYPES: Final = { name: RPrimitive(name, is_unboxed=False, is_refcounted=True) - for name in ["librt.internal.WriteBuffer", "librt.internal.ReadBuffer"] + for name in [ + "librt.internal.WriteBuffer", + "librt.internal.ReadBuffer", + "librt.strings.BytesWriter", + ] } diff --git a/mypyc/lib-rt/librt_strings.c b/mypyc/lib-rt/librt_strings.c new file mode 100644 index 000000000000..393337fe914d --- /dev/null +++ b/mypyc/lib-rt/librt_strings.c @@ -0,0 +1,344 @@ +#include "pythoncapi_compat.h" + +#define PY_SSIZE_T_CLEAN +#include +#include +#include "CPy.h" +#include "librt_strings.h" + +#define CPY_BOOL_ERROR 2 +#define CPY_NONE_ERROR 2 +#define CPY_NONE 1 + +// +// BytesWriter +// + +// Length of the default buffer embedded directly in a BytesWriter object +#define WRITER_EMBEDDED_BUF_LEN 512 + +typedef struct { + PyObject_HEAD + char *buf; // Beginning of the buffer + Py_ssize_t len; // Current length (number of bytes written) + Py_ssize_t capacity; // Total capacity of the buffer + char data[WRITER_EMBEDDED_BUF_LEN]; // Default buffer +} BytesWriterObject; + +#define _WRITE(data, type, v) \ + do { \ + *(type *)(((BytesWriterObject *)data)->buf + ((BytesWriterObject *)data)->len) = v; \ + ((BytesWriterObject *)data)->len += sizeof(type); \ + } while (0) + +static PyTypeObject BytesWriterType; + +static bool +_grow_buffer(BytesWriterObject *data, Py_ssize_t n) { + Py_ssize_t target = data->len + n; + Py_ssize_t size = data->capacity; + Py_ssize_t old_size = size; + do { + size *= 2; + } while (target >= size); + if (old_size == WRITER_EMBEDDED_BUF_LEN) { + // Move from embedded buffer to heap-allocated buffer + data->buf = PyMem_Malloc(size); + if (data->buf != NULL) { + memcpy(data->buf, data->data, WRITER_EMBEDDED_BUF_LEN); + } + } else { + data->buf = PyMem_Realloc(data->buf, size); + } + if (unlikely(data->buf == NULL)) { + PyErr_NoMemory(); + return false; + } + data->capacity = size; + return true; +} + +static inline bool +ensure_bytes_writer_size(BytesWriterObject *data, Py_ssize_t n) { + if (likely(data->capacity - data->len >= n)) { + return true; + } else { + return _grow_buffer(data, n); + } +} + +static inline void +BytesWriter_init_internal(BytesWriterObject *self) { + self->buf = self->data; + self->len = 0; + self->capacity = WRITER_EMBEDDED_BUF_LEN; +} + +static PyObject* +BytesWriter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + if (type != &BytesWriterType) { + PyErr_SetString(PyExc_TypeError, "BytesWriter cannot be subclassed"); + return NULL; + } + + BytesWriterObject *self = (BytesWriterObject *)type->tp_alloc(type, 0); + if (self != NULL) { + BytesWriter_init_internal(self); + } + return (PyObject *)self; +} + +static PyObject * +BytesWriter_internal(void) { + BytesWriterObject *self = (BytesWriterObject *)BytesWriterType.tp_alloc(&BytesWriterType, 0); + if (self == NULL) + return NULL; + BytesWriter_init_internal(self); + return (PyObject *)self; +} + +static int +BytesWriter_init(BytesWriterObject *self, PyObject *args, PyObject *kwds) +{ + if (!PyArg_ParseTuple(args, "")) { + return -1; + } + + if (kwds != NULL && PyDict_Size(kwds) > 0) { + PyErr_SetString(PyExc_TypeError, + "BytesWriter() takes no keyword arguments"); + return -1; + } + + BytesWriter_init_internal(self); + return 0; +} + +static void +BytesWriter_dealloc(BytesWriterObject *self) +{ + if (self->buf != self->data) { + PyMem_Free(self->buf); + self->buf = NULL; + } + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject* +BytesWriter_getvalue_internal(PyObject *self) +{ + BytesWriterObject *obj = (BytesWriterObject *)self; + return PyBytes_FromStringAndSize(obj->buf, obj->len); +} + +static PyObject* +BytesWriter_repr(BytesWriterObject *self) +{ + PyObject *value = BytesWriter_getvalue_internal((PyObject *)self); + if (value == NULL) { + return NULL; + } + PyObject *value_repr = PyObject_Repr(value); + Py_DECREF(value); + if (value_repr == NULL) { + return NULL; + } + PyObject *result = PyUnicode_FromFormat("BytesWriter(%U)", value_repr); + Py_DECREF(value_repr); + return result; +} + +static PyObject* +BytesWriter_getvalue(BytesWriterObject *self, PyObject *Py_UNUSED(ignored)) +{ + return PyBytes_FromStringAndSize(self->buf, self->len); +} + +static PyObject* BytesWriter_append(PyObject *self, PyObject *const *args, size_t nargs, PyObject *kwnames); +static PyObject* BytesWriter_write(PyObject *self, PyObject *const *args, size_t nargs, PyObject *kwnames); + +static PyMethodDef BytesWriter_methods[] = { + {"append", (PyCFunction) BytesWriter_append, METH_FASTCALL | METH_KEYWORDS, + PyDoc_STR("Append a single byte to the buffer") + }, + {"write", (PyCFunction) BytesWriter_write, METH_FASTCALL | METH_KEYWORDS, + PyDoc_STR("Append bytes to the buffer") + }, + {"getvalue", (PyCFunction) BytesWriter_getvalue, METH_NOARGS, + "Return the buffer content as bytes object" + }, + {NULL} /* Sentinel */ +}; + +static PyTypeObject BytesWriterType = { + .ob_base = PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "BytesWriter", + .tp_doc = PyDoc_STR("Memory buffer for building bytes objects from parts"), + .tp_basicsize = sizeof(BytesWriterObject), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = BytesWriter_new, + .tp_init = (initproc) BytesWriter_init, + .tp_dealloc = (destructor) BytesWriter_dealloc, + .tp_methods = BytesWriter_methods, + .tp_repr = (reprfunc)BytesWriter_repr, +}; + +static inline bool +check_bytes_writer(PyObject *data) { + if (unlikely(Py_TYPE(data) != &BytesWriterType)) { + PyErr_Format( + PyExc_TypeError, "data must be a BytesWriter object, got %s", Py_TYPE(data)->tp_name + ); + return false; + } + return true; +} + +static char +BytesWriter_write_internal(BytesWriterObject *self, PyObject *value) { + const char *data; + Py_ssize_t size; + if (likely(PyBytes_Check(value))) { + data = PyBytes_AS_STRING(value); + size = PyBytes_GET_SIZE(value); + } else { + data = PyByteArray_AS_STRING(value); + size = PyByteArray_GET_SIZE(value); + } + // Write bytes content. + if (!ensure_bytes_writer_size(self, size)) + return CPY_NONE_ERROR; + memcpy(self->buf + self->len, data, size); + self->len += size; + return CPY_NONE; +} + +static PyObject* +BytesWriter_write(PyObject *self, PyObject *const *args, size_t nargs, PyObject *kwnames) { + static const char * const kwlist[] = {"value", 0}; + static CPyArg_Parser parser = {"O:write", kwlist, 0}; + PyObject *value; + if (unlikely(!CPyArg_ParseStackAndKeywordsSimple(args, nargs, kwnames, &parser, &value))) { + return NULL; + } + if (!check_bytes_writer(self)) { + return NULL; + } + if (unlikely(!PyBytes_Check(value) && !PyByteArray_Check(value))) { + PyErr_SetString(PyExc_TypeError, "value must be a bytes or bytearray object"); + return NULL; + } + if (unlikely(BytesWriter_write_internal((BytesWriterObject *)self, value) == CPY_NONE_ERROR)) { + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +static inline char +BytesWriter_append_internal(BytesWriterObject *self, uint8_t value) { + if (!ensure_bytes_writer_size(self, 1)) + return CPY_NONE_ERROR; + _WRITE(self, uint8_t, value); + return CPY_NONE; +} + +static PyObject* +BytesWriter_append(PyObject *self, PyObject *const *args, size_t nargs, PyObject *kwnames) { + static const char * const kwlist[] = {"value", 0}; + static CPyArg_Parser parser = {"O:append", kwlist, 0}; + PyObject *value; + if (unlikely(!CPyArg_ParseStackAndKeywordsSimple(args, nargs, kwnames, &parser, &value))) { + return NULL; + } + if (!check_bytes_writer(self)) { + return NULL; + } + uint8_t unboxed = CPyLong_AsUInt8(value); + if (unlikely(unboxed == CPY_LL_UINT_ERROR && PyErr_Occurred())) { + CPy_TypeError("u8", value); + return NULL; + } + if (unlikely(BytesWriter_append_internal((BytesWriterObject *)self, unboxed) == CPY_NONE_ERROR)) { + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +static PyTypeObject * +BytesWriter_type_internal(void) { + return &BytesWriterType; // Return borrowed reference +}; + +static PyMethodDef librt_strings_module_methods[] = { + {NULL, NULL, 0, NULL} +}; + +#ifdef MYPYC_EXPERIMENTAL + +static int +strings_abi_version(void) { + return LIBRT_STRINGS_ABI_VERSION; +} + +static int +strings_api_version(void) { + return LIBRT_STRINGS_API_VERSION; +} + +#endif + +static int +librt_strings_module_exec(PyObject *m) +{ +#ifdef MYPYC_EXPERIMENTAL + if (PyType_Ready(&BytesWriterType) < 0) { + return -1; + } + if (PyModule_AddObjectRef(m, "BytesWriter", (PyObject *) &BytesWriterType) < 0) { + return -1; + } + + // Export mypy internal C API, be careful with the order! + static void *librt_strings_api[LIBRT_STRINGS_API_LEN] = { + (void *)strings_abi_version, + (void *)strings_api_version, + (void *)BytesWriter_internal, + (void *)BytesWriter_getvalue_internal, + (void *)BytesWriter_append_internal, + (void *)BytesWriter_write_internal, + (void *)BytesWriter_type_internal, + }; + PyObject *c_api_object = PyCapsule_New((void *)librt_strings_api, "librt.strings._C_API", NULL); + if (PyModule_Add(m, "_C_API", c_api_object) < 0) { + return -1; + } +#endif + return 0; +} + +static PyModuleDef_Slot librt_strings_module_slots[] = { + {Py_mod_exec, librt_strings_module_exec}, +#ifdef Py_MOD_GIL_NOT_USED + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif + {0, NULL} +}; + +static PyModuleDef librt_strings_module = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "strings", + .m_doc = "Utilities for working with str and bytes objects", + .m_size = 0, + .m_methods = librt_strings_module_methods, + .m_slots = librt_strings_module_slots, +}; + +PyMODINIT_FUNC +PyInit_strings(void) +{ + return PyModuleDef_Init(&librt_strings_module); +} diff --git a/mypyc/lib-rt/librt_strings.h b/mypyc/lib-rt/librt_strings.h new file mode 100644 index 000000000000..fe359f1d3662 --- /dev/null +++ b/mypyc/lib-rt/librt_strings.h @@ -0,0 +1,78 @@ +#ifndef LIBRT_STRINGS_H +#define LIBRT_STRINGS_H + +#ifndef MYPYC_EXPERIMENTAL + +static int +import_librt_strings(void) +{ + // All librt.base64 features are experimental for now, so don't set up the API here + return 0; +} + +#else // MYPYC_EXPERIMENTAL + +// ABI version -- only an exact match is compatible. This will only be changed in +// very exceptional cases (likely never) due to strict backward compatibility +// requirements. +#define LIBRT_STRINGS_ABI_VERSION 0 + +// API version -- more recent versions must maintain backward compatibility, i.e. +// we can add new features but not remove or change existing features (unless +// ABI version is changed, but see the comment above). + #define LIBRT_STRINGS_API_VERSION 0 + +// Number of functions in the capsule API. If you add a new function, also increase +// LIBRT_STRINGS_API_VERSION. +#define LIBRT_STRINGS_API_LEN 7 + +static void *LibRTStrings_API[LIBRT_STRINGS_API_LEN]; + +#define LibRTStrings_ABIVersion (*(int (*)(void)) LibRTStrings_API[0]) +#define LibRTStrings_APIVersion (*(int (*)(void)) LibRTStrings_API[1]) +#define LibRTStrings_BytesWriter_internal (*(PyObject* (*)(void)) LibRTStrings_API[2]) +#define LibRTStrings_BytesWriter_getvalue_internal (*(PyObject* (*)(PyObject *source)) LibRTStrings_API[3]) +#define LibRTStrings_BytesWriter_append_internal (*(char (*)(PyObject *source, uint8_t value)) LibRTStrings_API[4]) +#define LibRTStrings_BytesWriter_write_internal (*(char (*)(PyObject *source, PyObject *value)) LibRTStrings_API[5]) +#define LibRTStrings_BytesWriter_type_internal (*(PyTypeObject* (*)(void)) LibRTStrings_API[6]) + +static int +import_librt_strings(void) +{ + PyObject *mod = PyImport_ImportModule("librt.strings"); + if (mod == NULL) + return -1; + Py_DECREF(mod); // we import just for the side effect of making the below work. + void *capsule = PyCapsule_Import("librt.strings._C_API", 0); + if (capsule == NULL) + return -1; + memcpy(LibRTStrings_API, capsule, sizeof(LibRTStrings_API)); + if (LibRTStrings_ABIVersion() != LIBRT_STRINGS_ABI_VERSION) { + char err[128]; + snprintf(err, sizeof(err), "ABI version conflict for librt.strings, expected %d, found %d", + LIBRT_STRINGS_ABI_VERSION, + LibRTStrings_ABIVersion() + ); + PyErr_SetString(PyExc_ValueError, err); + return -1; + } + if (LibRTStrings_APIVersion() < LIBRT_STRINGS_API_VERSION) { + char err[128]; + snprintf(err, sizeof(err), + "API version conflict for librt.strings, expected %d or newer, found %d (hint: upgrade librt)", + LIBRT_STRINGS_API_VERSION, + LibRTStrings_APIVersion() + ); + PyErr_SetString(PyExc_ValueError, err); + return -1; + } + return 0; +} + +static inline bool CPyBytesWriter_Check(PyObject *obj) { + return Py_TYPE(obj) == LibRTStrings_BytesWriter_type_internal(); +} + +#endif // MYPYC_EXPERIMENTAL + +#endif // LIBRT_STRINGS_H diff --git a/mypyc/lib-rt/setup.py b/mypyc/lib-rt/setup.py index 6a56c65306ae..c28c231a5f0b 100644 --- a/mypyc/lib-rt/setup.py +++ b/mypyc/lib-rt/setup.py @@ -104,6 +104,19 @@ def run(self) -> None: include_dirs=["."], extra_compile_args=cflags, ), + Extension( + "librt.strings", + [ + "librt_strings.c", + "init.c", + "int_ops.c", + "exc_ops.c", + "pythonsupport.c", + "getargsfast.c", + ], + include_dirs=["."], + extra_compile_args=cflags, + ), Extension( "librt.base64", [ diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py new file mode 100644 index 000000000000..bf217504106f --- /dev/null +++ b/mypyc/primitives/librt_strings_ops.py @@ -0,0 +1,47 @@ +from typing import Final + +from mypyc.ir.ops import ERR_MAGIC +from mypyc.ir.rtypes import KNOWN_NATIVE_TYPES, bytes_rprimitive, none_rprimitive, uint8_rprimitive +from mypyc.primitives.registry import function_op, method_op + +bytes_writer_rprimitive: Final = KNOWN_NATIVE_TYPES["librt.strings.BytesWriter"] + +function_op( + name="librt.strings.BytesWriter", + arg_types=[], + return_type=bytes_writer_rprimitive, + c_function_name="LibRTStrings_BytesWriter_internal", + error_kind=ERR_MAGIC, + experimental=True, + capsule="librt.strings", +) + +method_op( + name="getvalue", + arg_types=[bytes_writer_rprimitive], + return_type=bytes_rprimitive, + c_function_name="LibRTStrings_BytesWriter_getvalue_internal", + error_kind=ERR_MAGIC, + experimental=True, + capsule="librt.strings", +) + +method_op( + name="write", + arg_types=[bytes_writer_rprimitive, bytes_rprimitive], + return_type=none_rprimitive, + c_function_name="LibRTStrings_BytesWriter_write_internal", + error_kind=ERR_MAGIC, + experimental=True, + capsule="librt.strings", +) + +method_op( + name="append", + arg_types=[bytes_writer_rprimitive, uint8_rprimitive], + return_type=none_rprimitive, + c_function_name="LibRTStrings_BytesWriter_append_internal", + error_kind=ERR_MAGIC, + experimental=True, + capsule="librt.strings", +) diff --git a/mypyc/primitives/registry.py b/mypyc/primitives/registry.py index 2f66b1915501..a83e24aea697 100644 --- a/mypyc/primitives/registry.py +++ b/mypyc/primitives/registry.py @@ -101,6 +101,7 @@ def method_op( is_borrowed: bool = False, priority: int = 1, is_pure: bool = False, + experimental: bool = False, capsule: str | None = None, ) -> PrimitiveDescription: """Define a c function call op that replaces a method call. @@ -146,7 +147,7 @@ def method_op( extra_int_constants, priority, is_pure=is_pure, - experimental=False, + experimental=experimental, capsule=capsule, ) ops.append(desc) @@ -389,6 +390,7 @@ def load_address_op(name: str, type: RType, src: str) -> LoadAddressDescription: import mypyc.primitives.dict_ops import mypyc.primitives.float_ops import mypyc.primitives.int_ops +import mypyc.primitives.librt_strings_ops import mypyc.primitives.list_ops import mypyc.primitives.misc_ops import mypyc.primitives.str_ops diff --git a/mypyc/test-data/irbuild-librt-strings.test b/mypyc/test-data/irbuild-librt-strings.test new file mode 100644 index 000000000000..e72037926d3e --- /dev/null +++ b/mypyc/test-data/irbuild-librt-strings.test @@ -0,0 +1,27 @@ +[case testLibrtStrings_experimental] +from librt.strings import BytesWriter +from mypy_extensions import u8 + +def bytes_builder_basics() -> bytes: + b = BytesWriter() + x: u8 = 1 + b.append(x) + b.write(b'foo') + return b.getvalue() +[out] +def bytes_builder_basics(): + r0, b :: librt.strings.BytesWriter + x :: u8 + r1 :: None + r2 :: bytes + r3 :: None + r4 :: bytes +L0: + r0 = LibRTStrings_BytesWriter_internal() + b = r0 + x = 1 + r1 = LibRTStrings_BytesWriter_append_internal(b, x) + r2 = b'foo' + r3 = LibRTStrings_BytesWriter_write_internal(b, r2) + r4 = LibRTStrings_BytesWriter_getvalue_internal(b) + return r4 diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test new file mode 100644 index 000000000000..8d10bcd40d03 --- /dev/null +++ b/mypyc/test-data/run-librt-strings.test @@ -0,0 +1,95 @@ +[case testLibrtStrings_librt_experimental] +from typing import Any +import base64 +import binascii +import random + +from librt.strings import BytesWriter + +from testutil import assertRaises + +def test_bytes_writer_basics() -> None: + w = BytesWriter() + assert w.getvalue() == b"" + assert repr(w) == "BytesWriter(b'')" + + w = BytesWriter() + w.append(ord('a')) + w.write(b'bc') + assert w.getvalue() == b"abc" + assert repr(w) == "BytesWriter(b'abc')" + +def test_bytes_writer_append_grow() -> None: + w = BytesWriter() + for i in range(16384): + w.append((i ^ (i >> 8)) & 255) + b = w.getvalue() + for i in range(16384): + assert b[i] == (i ^ (i >> 8)) & 255 + +def test_bytes_writer_write_grow() -> None: + w = BytesWriter() + for i in range(16384): + w.write(bytes([(i ^ (i >> 8)) & 255])) + b = w.getvalue() + for i in range(16384): + assert b[i] == (i ^ (i >> 8)) & 255 + + w = BytesWriter() + a = [] + for i in range(16384): + w.write(bytes()) + if i & 1 == 0: + segment = b"foobarz" + else: + segment = b"\x7f\x00ab!" + w.write(segment) + a.append(segment) + assert w.getvalue() == b"".join(a) + +def test_write_bytearray() -> None: + w = BytesWriter() + w.write(bytearray(b"foobar")) + w.write(bytearray(b"")) + w.write(bytearray(b"\x00\xf8")) + assert w.getvalue() == b"foobar\x00\xf8" + +def test_cast_bytes_writer() -> None: + a: Any = BytesWriter() + b: BytesWriter = a + assert b.getvalue() == b"" + + a2: Any = "x" + with assertRaises(TypeError): + b = a2 + +def test_bytes_writer_wrapper_functions() -> None: + cls: Any = BytesWriter + b: Any = cls() + assert repr(b) == "BytesWriter(b'')" + b.append(ord('a')) + b.append(0) + b.append(255) + b.write(b"foo") + b.write(bytearray(b"bar")) + assert b.getvalue() == b"a\x00\xfffoobar" + assert isinstance(b, cls) + + with assertRaises(TypeError): + b.append("x") + + with assertRaises(TypeError): + b.write("foo") + + with assertRaises(TypeError): + b.append(256) + + with assertRaises(TypeError): + b.append(-1) + +[case testStringsFeaturesNotAvailableInNonExperimentalBuild_librt] +# This also ensures librt.strings can be built without experimental features +import librt.strings + +def test_bytes_writer_not_available() -> None: + assert not hasattr(librt.strings, "BytesWriter") diff --git a/mypyc/test/test_irbuild.py b/mypyc/test/test_irbuild.py index 7c248640246d..b0ede4569164 100644 --- a/mypyc/test/test_irbuild.py +++ b/mypyc/test/test_irbuild.py @@ -54,6 +54,7 @@ "irbuild-glue-methods.test", "irbuild-math.test", "irbuild-weakref.test", + "irbuild-librt-strings.test", "irbuild-base64.test", ] diff --git a/mypyc/test/test_run.py b/mypyc/test/test_run.py index 6b63a4d546d0..27f7674d68c8 100644 --- a/mypyc/test/test_run.py +++ b/mypyc/test/test_run.py @@ -73,6 +73,7 @@ "run-weakref.test", "run-python37.test", "run-python38.test", + "run-librt-strings.test", "run-base64.test", ]