import builtins as bltns import functools import sys from types import NoneType from typing import Any from libclinic import fail, Null, unspecified, unknown from libclinic.function import ( Function, Parameter, CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW, GETTER, SETTER) from libclinic.codegen import CRenderData, TemplateDict from libclinic.converter import ( CConverter, legacy_converters, add_legacy_c_converter) TypeSet = set[bltns.type[object]] class bool_converter(CConverter): type = 'int' default_type = bool format_unit = 'p' c_ignored_default = '0' def converter_init(self, *, accept: TypeSet = {object}) -> None: if accept == {int}: self.format_unit = 'i' elif accept != {object}: fail(f"bool_converter: illegal 'accept' argument {accept!r}") if self.default is not unspecified and self.default is not unknown: self.default = bool(self.default) self.c_default = str(int(self.default)) def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'i': return self.format_code(""" {paramname} = PyLong_AsInt({argname}); if ({paramname} == -1 && PyErr_Occurred()) {{{{ goto exit; }}}} """, argname=argname) elif self.format_unit == 'p': return self.format_code(""" {paramname} = PyObject_IsTrue({argname}); if ({paramname} < 0) {{{{ goto exit; }}}} """, argname=argname) return super().parse_arg(argname, displayname, limited_capi=limited_capi) class defining_class_converter(CConverter): """ A special-case converter: this is the default converter used for the defining class. """ type = 'PyTypeObject *' format_unit = '' show_in_signature = False def converter_init(self, *, type: str | None = None) -> None: self.specified_type = type def render(self, parameter: Parameter, data: CRenderData) -> None: self._render_self(parameter, data) def set_template_dict(self, template_dict: TemplateDict) -> None: template_dict['defining_class_name'] = self.name class char_converter(CConverter): type = 'char' default_type = (bytes, bytearray) format_unit = 'c' c_ignored_default = "'\0'" def converter_init(self) -> None: if isinstance(self.default, self.default_type): if len(self.default) != 1: fail(f"char_converter: illegal default value {self.default!r}") self.c_default = repr(bytes(self.default))[1:] if self.c_default == '"\'"': self.c_default = r"'\''" def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'c': return self.format_code(""" if (PyBytes_Check({argname}) && PyBytes_GET_SIZE({argname}) == 1) {{{{ {paramname} = PyBytes_AS_STRING({argname})[0]; }}}} else if (PyByteArray_Check({argname}) && PyByteArray_GET_SIZE({argname}) == 1) {{{{ {paramname} = PyByteArray_AS_STRING({argname})[0]; }}}} else {{{{ {bad_argument} goto exit; }}}} """, argname=argname, bad_argument=self.bad_argument(displayname, 'a byte string of length 1', limited_capi=limited_capi), ) return super().parse_arg(argname, displayname, limited_capi=limited_capi) @add_legacy_c_converter('B', bitwise=True) class unsigned_char_converter(CConverter): type = 'unsigned char' default_type = int format_unit = 'b' c_ignored_default = "'\0'" def converter_init(self, *, bitwise: bool = False) -> None: if bitwise: self.format_unit = 'B' def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'b': return self.format_code(""" {{{{ long ival = PyLong_AsLong({argname}); if (ival == -1 && PyErr_Occurred()) {{{{ goto exit; }}}} else if (ival < 0) {{{{ PyErr_SetString(PyExc_OverflowError, "unsigned byte integer is less than minimum"); goto exit; }}}} else if (ival > UCHAR_MAX) {{{{ PyErr_SetString(PyExc_OverflowError, "unsigned byte integer is greater than maximum"); goto exit; }}}} else {{{{ {paramname} = (unsigned char) ival; }}}} }}}} """, argname=argname) elif self.format_unit == 'B': return self.format_code(""" {{{{ unsigned long ival = PyLong_AsUnsignedLongMask({argname}); if (ival == (unsigned long)-1 && PyErr_Occurred()) {{{{ goto exit; }}}} else {{{{ {paramname} = (unsigned char) ival; }}}} }}}} """, argname=argname) return super().parse_arg(argname, displayname, limited_capi=limited_capi) class byte_converter(unsigned_char_converter): pass class short_converter(CConverter): type = 'short' default_type = int format_unit = 'h' c_ignored_default = "0" def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'h': return self.format_code(""" {{{{ long ival = PyLong_AsLong({argname}); if (ival == -1 && PyErr_Occurred()) {{{{ goto exit; }}}} else if (ival < SHRT_MIN) {{{{ PyErr_SetString(PyExc_OverflowError, "signed short integer is less than minimum"); goto exit; }}}} else if (ival > SHRT_MAX) {{{{ PyErr_SetString(PyExc_OverflowError, "signed short integer is greater than maximum"); goto exit; }}}} else {{{{ {paramname} = (short) ival; }}}} }}}} """, argname=argname) return super().parse_arg(argname, displayname, limited_capi=limited_capi) class unsigned_short_converter(CConverter): type = 'unsigned short' default_type = int c_ignored_default = "0" def converter_init(self, *, bitwise: bool = False) -> None: if bitwise: self.format_unit = 'H' else: self.converter = '_PyLong_UnsignedShort_Converter' def use_converter(self) -> None: if self.converter == '_PyLong_UnsignedShort_Converter': self.add_include('pycore_long.h', '_PyLong_UnsignedShort_Converter()') def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'H': return self.format_code(""" {paramname} = (unsigned short)PyLong_AsUnsignedLongMask({argname}); if ({paramname} == (unsigned short)-1 && PyErr_Occurred()) {{{{ goto exit; }}}} """, argname=argname) if not limited_capi: return super().parse_arg(argname, displayname, limited_capi=limited_capi) # NOTE: Raises OverflowError for negative integer. return self.format_code(""" {{{{ unsigned long uval = PyLong_AsUnsignedLong({argname}); if (uval == (unsigned long)-1 && PyErr_Occurred()) {{{{ goto exit; }}}} if (uval > USHRT_MAX) {{{{ PyErr_SetString(PyExc_OverflowError, "Python int too large for C unsigned short"); goto exit; }}}} {paramname} = (unsigned short) uval; }}}} """, argname=argname) @add_legacy_c_converter('C', accept={str}) class int_converter(CConverter): type = 'int' default_type = int format_unit = 'i' c_ignored_default = "0" def converter_init( self, *, accept: TypeSet = {int}, type: str | None = None ) -> None: if accept == {str}: self.format_unit = 'C' elif accept != {int}: fail(f"int_converter: illegal 'accept' argument {accept!r}") if type is not None: self.type = type def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'i': return self.format_code(""" {paramname} = PyLong_AsInt({argname}); if ({paramname} == -1 && PyErr_Occurred()) {{{{ goto exit; }}}} """, argname=argname) elif self.format_unit == 'C': return self.format_code(""" if (!PyUnicode_Check({argname})) {{{{ {bad_argument} goto exit; }}}} if (PyUnicode_GET_LENGTH({argname}) != 1) {{{{ {bad_argument} goto exit; }}}} {paramname} = PyUnicode_READ_CHAR({argname}, 0); """, argname=argname, bad_argument=self.bad_argument(displayname, 'a unicode character', limited_capi=limited_capi), ) return super().parse_arg(argname, displayname, limited_capi=limited_capi) class unsigned_int_converter(CConverter): type = 'unsigned int' default_type = int c_ignored_default = "0" def converter_init(self, *, bitwise: bool = False) -> None: if bitwise: self.format_unit = 'I' else: self.converter = '_PyLong_UnsignedInt_Converter' def use_converter(self) -> None: if self.converter == '_PyLong_UnsignedInt_Converter': self.add_include('pycore_long.h', '_PyLong_UnsignedInt_Converter()') def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'I': return self.format_code(""" {paramname} = (unsigned int)PyLong_AsUnsignedLongMask({argname}); if ({paramname} == (unsigned int)-1 && PyErr_Occurred()) {{{{ goto exit; }}}} """, argname=argname) if not limited_capi: return super().parse_arg(argname, displayname, limited_capi=limited_capi) # NOTE: Raises OverflowError for negative integer. return self.format_code(""" {{{{ unsigned long uval = PyLong_AsUnsignedLong({argname}); if (uval == (unsigned long)-1 && PyErr_Occurred()) {{{{ goto exit; }}}} if (uval > UINT_MAX) {{{{ PyErr_SetString(PyExc_OverflowError, "Python int too large for C unsigned int"); goto exit; }}}} {paramname} = (unsigned int) uval; }}}} """, argname=argname) class long_converter(CConverter): type = 'long' default_type = int format_unit = 'l' c_ignored_default = "0" def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'l': return self.format_code(""" {paramname} = PyLong_AsLong({argname}); if ({paramname} == -1 && PyErr_Occurred()) {{{{ goto exit; }}}} """, argname=argname) return super().parse_arg(argname, displayname, limited_capi=limited_capi) class unsigned_long_converter(CConverter): type = 'unsigned long' default_type = int c_ignored_default = "0" def converter_init(self, *, bitwise: bool = False) -> None: if bitwise: self.format_unit = 'k' else: self.converter = '_PyLong_UnsignedLong_Converter' def use_converter(self) -> None: if self.converter == '_PyLong_UnsignedLong_Converter': self.add_include('pycore_long.h', '_PyLong_UnsignedLong_Converter()') def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'k': return self.format_code(""" if (!PyLong_Check({argname})) {{{{ {bad_argument} goto exit; }}}} {paramname} = PyLong_AsUnsignedLongMask({argname}); """, argname=argname, bad_argument=self.bad_argument(displayname, 'int', limited_capi=limited_capi), ) if not limited_capi: return super().parse_arg(argname, displayname, limited_capi=limited_capi) # NOTE: Raises OverflowError for negative integer. return self.format_code(""" {paramname} = PyLong_AsUnsignedLong({argname}); if ({paramname} == (unsigned long)-1 && PyErr_Occurred()) {{{{ goto exit; }}}} """, argname=argname) class long_long_converter(CConverter): type = 'long long' default_type = int format_unit = 'L' c_ignored_default = "0" def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'L': return self.format_code(""" {paramname} = PyLong_AsLongLong({argname}); if ({paramname} == -1 && PyErr_Occurred()) {{{{ goto exit; }}}} """, argname=argname) return super().parse_arg(argname, displayname, limited_capi=limited_capi) class unsigned_long_long_converter(CConverter): type = 'unsigned long long' default_type = int c_ignored_default = "0" def converter_init(self, *, bitwise: bool = False) -> None: if bitwise: self.format_unit = 'K' else: self.converter = '_PyLong_UnsignedLongLong_Converter' def use_converter(self) -> None: if self.converter == '_PyLong_UnsignedLongLong_Converter': self.add_include('pycore_long.h', '_PyLong_UnsignedLongLong_Converter()') def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'K': return self.format_code(""" if (!PyLong_Check({argname})) {{{{ {bad_argument} goto exit; }}}} {paramname} = PyLong_AsUnsignedLongLongMask({argname}); """, argname=argname, bad_argument=self.bad_argument(displayname, 'int', limited_capi=limited_capi), ) if not limited_capi: return super().parse_arg(argname, displayname, limited_capi=limited_capi) # NOTE: Raises OverflowError for negative integer. return self.format_code(""" {paramname} = PyLong_AsUnsignedLongLong({argname}); if ({paramname} == (unsigned long long)-1 && PyErr_Occurred()) {{{{ goto exit; }}}} """, argname=argname) class Py_ssize_t_converter(CConverter): type = 'Py_ssize_t' c_ignored_default = "0" def converter_init(self, *, accept: TypeSet = {int}) -> None: if accept == {int}: self.format_unit = 'n' self.default_type = int elif accept == {int, NoneType}: self.converter = '_Py_convert_optional_to_ssize_t' else: fail(f"Py_ssize_t_converter: illegal 'accept' argument {accept!r}") def use_converter(self) -> None: if self.converter == '_Py_convert_optional_to_ssize_t': self.add_include('pycore_abstract.h', '_Py_convert_optional_to_ssize_t()') def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'n': if limited_capi: PyNumber_Index = 'PyNumber_Index' else: PyNumber_Index = '_PyNumber_Index' self.add_include('pycore_abstract.h', '_PyNumber_Index()') return self.format_code(""" {{{{ Py_ssize_t ival = -1; PyObject *iobj = {PyNumber_Index}({argname}); if (iobj != NULL) {{{{ ival = PyLong_AsSsize_t(iobj); Py_DECREF(iobj); }}}} if (ival == -1 && PyErr_Occurred()) {{{{ goto exit; }}}} {paramname} = ival; }}}} """, argname=argname, PyNumber_Index=PyNumber_Index) if not limited_capi: return super().parse_arg(argname, displayname, limited_capi=limited_capi) return self.format_code(""" if ({argname} != Py_None) {{{{ if (PyIndex_Check({argname})) {{{{ {paramname} = PyNumber_AsSsize_t({argname}, PyExc_OverflowError); if ({paramname} == -1 && PyErr_Occurred()) {{{{ goto exit; }}}} }}}} else {{{{ {bad_argument} goto exit; }}}} }}}} """, argname=argname, bad_argument=self.bad_argument(displayname, 'integer or None', limited_capi=limited_capi), ) class slice_index_converter(CConverter): type = 'Py_ssize_t' def converter_init(self, *, accept: TypeSet = {int, NoneType}) -> None: if accept == {int}: self.converter = '_PyEval_SliceIndexNotNone' self.nullable = False elif accept == {int, NoneType}: self.converter = '_PyEval_SliceIndex' self.nullable = True else: fail(f"slice_index_converter: illegal 'accept' argument {accept!r}") def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if not limited_capi: return super().parse_arg(argname, displayname, limited_capi=limited_capi) if self.nullable: return self.format_code(""" if (!Py_IsNone({argname})) {{{{ if (PyIndex_Check({argname})) {{{{ {paramname} = PyNumber_AsSsize_t({argname}, NULL); if ({paramname} == -1 && PyErr_Occurred()) {{{{ return 0; }}}} }}}} else {{{{ PyErr_SetString(PyExc_TypeError, "slice indices must be integers or " "None or have an __index__ method"); goto exit; }}}} }}}} """, argname=argname) else: return self.format_code(""" if (PyIndex_Check({argname})) {{{{ {paramname} = PyNumber_AsSsize_t({argname}, NULL); if ({paramname} == -1 && PyErr_Occurred()) {{{{ goto exit; }}}} }}}} else {{{{ PyErr_SetString(PyExc_TypeError, "slice indices must be integers or " "have an __index__ method"); goto exit; }}}} """, argname=argname) class size_t_converter(CConverter): type = 'size_t' converter = '_PyLong_Size_t_Converter' c_ignored_default = "0" def use_converter(self) -> None: self.add_include('pycore_long.h', '_PyLong_Size_t_Converter()') def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'n': return self.format_code(""" {paramname} = PyNumber_AsSsize_t({argname}, PyExc_OverflowError); if ({paramname} == -1 && PyErr_Occurred()) {{{{ goto exit; }}}} """, argname=argname) if not limited_capi: return super().parse_arg(argname, displayname, limited_capi=limited_capi) # NOTE: Raises OverflowError for negative integer. return self.format_code(""" {paramname} = PyLong_AsSize_t({argname}); if ({paramname} == (size_t)-1 && PyErr_Occurred()) {{{{ goto exit; }}}} """, argname=argname) class fildes_converter(CConverter): type = 'int' converter = '_PyLong_FileDescriptor_Converter' def use_converter(self) -> None: self.add_include('pycore_fileutils.h', '_PyLong_FileDescriptor_Converter()') def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: return self.format_code(""" {paramname} = PyObject_AsFileDescriptor({argname}); if ({paramname} < 0) {{{{ goto exit; }}}} """, argname=argname) class float_converter(CConverter): type = 'float' default_type = float format_unit = 'f' c_ignored_default = "0.0" def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'f': if not limited_capi: return self.format_code(""" if (PyFloat_CheckExact({argname})) {{{{ {paramname} = (float) (PyFloat_AS_DOUBLE({argname})); }}}} else {{{{ {paramname} = (float) PyFloat_AsDouble({argname}); if ({paramname} == -1.0 && PyErr_Occurred()) {{{{ goto exit; }}}} }}}} """, argname=argname) else: return self.format_code(""" {paramname} = (float) PyFloat_AsDouble({argname}); if ({paramname} == -1.0 && PyErr_Occurred()) {{{{ goto exit; }}}} """, argname=argname) return super().parse_arg(argname, displayname, limited_capi=limited_capi) class double_converter(CConverter): type = 'double' default_type = float format_unit = 'd' c_ignored_default = "0.0" def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'd': if not limited_capi: return self.format_code(""" if (PyFloat_CheckExact({argname})) {{{{ {paramname} = PyFloat_AS_DOUBLE({argname}); }}}} else {{{{ {paramname} = PyFloat_AsDouble({argname}); if ({paramname} == -1.0 && PyErr_Occurred()) {{{{ goto exit; }}}} }}}} """, argname=argname) else: return self.format_code(""" {paramname} = PyFloat_AsDouble({argname}); if ({paramname} == -1.0 && PyErr_Occurred()) {{{{ goto exit; }}}} """, argname=argname) return super().parse_arg(argname, displayname, limited_capi=limited_capi) class Py_complex_converter(CConverter): type = 'Py_complex' default_type = complex format_unit = 'D' c_ignored_default = "{0.0, 0.0}" def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'D': return self.format_code(""" {paramname} = PyComplex_AsCComplex({argname}); if (PyErr_Occurred()) {{{{ goto exit; }}}} """, argname=argname) return super().parse_arg(argname, displayname, limited_capi=limited_capi) class object_converter(CConverter): type = 'PyObject *' format_unit = 'O' def converter_init( self, *, converter: str | None = None, type: str | None = None, subclass_of: str | None = None ) -> None: if converter: if subclass_of: fail("object: Cannot pass in both 'converter' and 'subclass_of'") self.format_unit = 'O&' self.converter = converter elif subclass_of: self.format_unit = 'O!' self.subclass_of = subclass_of if type is not None: self.type = type # # We define three conventions for buffer types in the 'accept' argument: # # buffer : any object supporting the buffer interface # rwbuffer: any object supporting the buffer interface, but must be writeable # robuffer: any object supporting the buffer interface, but must not be writeable # class buffer: pass class rwbuffer: pass class robuffer: pass StrConverterKeyType = tuple[frozenset[type[object]], bool, bool] def str_converter_key( types: TypeSet, encoding: bool | str | None, zeroes: bool ) -> StrConverterKeyType: return (frozenset(types), bool(encoding), bool(zeroes)) str_converter_argument_map: dict[StrConverterKeyType, str] = {} class str_converter(CConverter): type = 'const char *' default_type = (str, Null, NoneType) format_unit = 's' def converter_init( self, *, accept: TypeSet = {str}, encoding: str | None = None, zeroes: bool = False ) -> None: key = str_converter_key(accept, encoding, zeroes) format_unit = str_converter_argument_map.get(key) if not format_unit: fail("str_converter: illegal combination of arguments", key) self.format_unit = format_unit self.length = bool(zeroes) if encoding: if self.default not in (Null, None, unspecified): fail("str_converter: Argument Clinic doesn't support default values for encoded strings") self.encoding = encoding self.type = 'char *' # sorry, clinic can't support preallocated buffers # for es# and et# self.c_default = "NULL" if NoneType in accept and self.c_default == "Py_None": self.c_default = "NULL" def post_parsing(self) -> str: if self.encoding: name = self.name return f"PyMem_FREE({name});\n" else: return "" def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 's': return self.format_code(""" if (!PyUnicode_Check({argname})) {{{{ {bad_argument} goto exit; }}}} Py_ssize_t {length_name}; {paramname} = PyUnicode_AsUTF8AndSize({argname}, &{length_name}); if ({paramname} == NULL) {{{{ goto exit; }}}} if (strlen({paramname}) != (size_t){length_name}) {{{{ PyErr_SetString(PyExc_ValueError, "embedded null character"); goto exit; }}}} """, argname=argname, bad_argument=self.bad_argument(displayname, 'str', limited_capi=limited_capi), length_name=self.length_name) if self.format_unit == 'z': return self.format_code(""" if ({argname} == Py_None) {{{{ {paramname} = NULL; }}}} else if (PyUnicode_Check({argname})) {{{{ Py_ssize_t {length_name}; {paramname} = PyUnicode_AsUTF8AndSize({argname}, &{length_name}); if ({paramname} == NULL) {{{{ goto exit; }}}} if (strlen({paramname}) != (size_t){length_name}) {{{{ PyErr_SetString(PyExc_ValueError, "embedded null character"); goto exit; }}}} }}}} else {{{{ {bad_argument} goto exit; }}}} """, argname=argname, bad_argument=self.bad_argument(displayname, 'str or None', limited_capi=limited_capi), length_name=self.length_name) return super().parse_arg(argname, displayname, limited_capi=limited_capi) # # This is the fourth or fifth rewrite of registering all the # string converter format units. Previous approaches hid # bugs--generally mismatches between the semantics of the format # unit and the arguments necessary to represent those semantics # properly. Hopefully with this approach we'll get it 100% right. # # The r() function (short for "register") both registers the # mapping from arguments to format unit *and* registers the # legacy C converter for that format unit. # def r(format_unit: str, *, accept: TypeSet, encoding: bool = False, zeroes: bool = False ) -> None: if not encoding and format_unit != 's': # add the legacy c converters here too. # # note: add_legacy_c_converter can't work for # es, es#, et, or et# # because of their extra encoding argument # # also don't add the converter for 's' because # the metaclass for CConverter adds it for us. kwargs: dict[str, Any] = {} if accept != {str}: kwargs['accept'] = accept if zeroes: kwargs['zeroes'] = True added_f = functools.partial(str_converter, **kwargs) legacy_converters[format_unit] = added_f d = str_converter_argument_map key = str_converter_key(accept, encoding, zeroes) if key in d: sys.exit("Duplicate keys specified for str_converter_argument_map!") d[key] = format_unit r('es', encoding=True, accept={str}) r('es#', encoding=True, zeroes=True, accept={str}) r('et', encoding=True, accept={bytes, bytearray, str}) r('et#', encoding=True, zeroes=True, accept={bytes, bytearray, str}) r('s', accept={str}) r('s#', zeroes=True, accept={robuffer, str}) r('y', accept={robuffer}) r('y#', zeroes=True, accept={robuffer}) r('z', accept={str, NoneType}) r('z#', zeroes=True, accept={robuffer, str, NoneType}) del r class PyBytesObject_converter(CConverter): type = 'PyBytesObject *' format_unit = 'S' # accept = {bytes} def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'S': return self.format_code(""" if (!PyBytes_Check({argname})) {{{{ {bad_argument} goto exit; }}}} {paramname} = ({type}){argname}; """, argname=argname, bad_argument=self.bad_argument(displayname, 'bytes', limited_capi=limited_capi), type=self.type) return super().parse_arg(argname, displayname, limited_capi=limited_capi) class PyByteArrayObject_converter(CConverter): type = 'PyByteArrayObject *' format_unit = 'Y' # accept = {bytearray} def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'Y': return self.format_code(""" if (!PyByteArray_Check({argname})) {{{{ {bad_argument} goto exit; }}}} {paramname} = ({type}){argname}; """, argname=argname, bad_argument=self.bad_argument(displayname, 'bytearray', limited_capi=limited_capi), type=self.type) return super().parse_arg(argname, displayname, limited_capi=limited_capi) class unicode_converter(CConverter): type = 'PyObject *' default_type = (str, Null, NoneType) format_unit = 'U' def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'U': return self.format_code(""" if (!PyUnicode_Check({argname})) {{{{ {bad_argument} goto exit; }}}} {paramname} = {argname}; """, argname=argname, bad_argument=self.bad_argument(displayname, 'str', limited_capi=limited_capi), ) return super().parse_arg(argname, displayname, limited_capi=limited_capi) @add_legacy_c_converter('u') @add_legacy_c_converter('u#', zeroes=True) @add_legacy_c_converter('Z', accept={str, NoneType}) @add_legacy_c_converter('Z#', accept={str, NoneType}, zeroes=True) class Py_UNICODE_converter(CConverter): type = 'const wchar_t *' default_type = (str, Null, NoneType) def converter_init( self, *, accept: TypeSet = {str}, zeroes: bool = False ) -> None: format_unit = 'Z' if accept=={str, NoneType} else 'u' if zeroes: format_unit += '#' self.length = True self.format_unit = format_unit else: self.accept = accept if accept == {str}: self.converter = '_PyUnicode_WideCharString_Converter' elif accept == {str, NoneType}: self.converter = '_PyUnicode_WideCharString_Opt_Converter' else: fail(f"Py_UNICODE_converter: illegal 'accept' argument {accept!r}") self.c_default = "NULL" def cleanup(self) -> str: if self.length: return "" else: return f"""PyMem_Free((void *){self.parser_name});\n""" def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if not self.length: if self.accept == {str}: return self.format_code(""" if (!PyUnicode_Check({argname})) {{{{ {bad_argument} goto exit; }}}} {paramname} = PyUnicode_AsWideCharString({argname}, NULL); if ({paramname} == NULL) {{{{ goto exit; }}}} """, argname=argname, bad_argument=self.bad_argument(displayname, 'str', limited_capi=limited_capi), ) elif self.accept == {str, NoneType}: return self.format_code(""" if ({argname} == Py_None) {{{{ {paramname} = NULL; }}}} else if (PyUnicode_Check({argname})) {{{{ {paramname} = PyUnicode_AsWideCharString({argname}, NULL); if ({paramname} == NULL) {{{{ goto exit; }}}} }}}} else {{{{ {bad_argument} goto exit; }}}} """, argname=argname, bad_argument=self.bad_argument(displayname, 'str or None', limited_capi=limited_capi), ) return super().parse_arg(argname, displayname, limited_capi=limited_capi) @add_legacy_c_converter('s*', accept={str, buffer}) @add_legacy_c_converter('z*', accept={str, buffer, NoneType}) @add_legacy_c_converter('w*', accept={rwbuffer}) class Py_buffer_converter(CConverter): type = 'Py_buffer' format_unit = 'y*' impl_by_reference = True c_ignored_default = "{NULL, NULL}" def converter_init(self, *, accept: TypeSet = {buffer}) -> None: if self.default not in (unspecified, None): fail("The only legal default value for Py_buffer is None.") self.c_default = self.c_ignored_default if accept == {str, buffer, NoneType}: format_unit = 'z*' elif accept == {str, buffer}: format_unit = 's*' elif accept == {buffer}: format_unit = 'y*' elif accept == {rwbuffer}: format_unit = 'w*' else: fail("Py_buffer_converter: illegal combination of arguments") self.format_unit = format_unit def cleanup(self) -> str: name = self.name return "".join(["if (", name, ".obj) {\n PyBuffer_Release(&", name, ");\n}\n"]) def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: # PyBUF_SIMPLE guarantees that the format units of the buffers are C-contiguous. if self.format_unit == 'y*': return self.format_code(""" if (PyObject_GetBuffer({argname}, &{paramname}, PyBUF_SIMPLE) != 0) {{{{ goto exit; }}}} """, argname=argname, bad_argument=self.bad_argument(displayname, 'contiguous buffer', limited_capi=limited_capi), ) elif self.format_unit == 's*': return self.format_code(""" if (PyUnicode_Check({argname})) {{{{ Py_ssize_t len; const char *ptr = PyUnicode_AsUTF8AndSize({argname}, &len); if (ptr == NULL) {{{{ goto exit; }}}} if (PyBuffer_FillInfo(&{paramname}, {argname}, (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) {{{{ goto exit; }}}} }}}} else {{{{ /* any bytes-like object */ if (PyObject_GetBuffer({argname}, &{paramname}, PyBUF_SIMPLE) != 0) {{{{ goto exit; }}}} }}}} """, argname=argname, bad_argument=self.bad_argument(displayname, 'contiguous buffer', limited_capi=limited_capi), ) elif self.format_unit == 'w*': return self.format_code(""" if (PyObject_GetBuffer({argname}, &{paramname}, PyBUF_WRITABLE) < 0) {{{{ {bad_argument} goto exit; }}}} """, argname=argname, bad_argument=self.bad_argument(displayname, 'read-write bytes-like object', limited_capi=limited_capi), bad_argument2=self.bad_argument(displayname, 'contiguous buffer', limited_capi=limited_capi), ) return super().parse_arg(argname, displayname, limited_capi=limited_capi) def correct_name_for_self( f: Function ) -> tuple[str, str]: if f.kind in {CALLABLE, METHOD_INIT, GETTER, SETTER}: if f.cls: return "PyObject *", "self" return "PyObject *", "module" if f.kind is STATIC_METHOD: return "void *", "null" if f.kind in (CLASS_METHOD, METHOD_NEW): return "PyTypeObject *", "type" raise AssertionError(f"Unhandled type of function f: {f.kind!r}") class self_converter(CConverter): """ A special-case converter: this is the default converter used for "self". """ type: str | None = None format_unit = '' def converter_init(self, *, type: str | None = None) -> None: self.specified_type = type def pre_render(self) -> None: f = self.function default_type, default_name = correct_name_for_self(f) self.signature_name = default_name self.type = self.specified_type or self.type or default_type kind = self.function.kind if kind is STATIC_METHOD or kind.new_or_init: self.show_in_signature = False # tp_new (METHOD_NEW) functions are of type newfunc: # typedef PyObject *(*newfunc)(PyTypeObject *, PyObject *, PyObject *); # # tp_init (METHOD_INIT) functions are of type initproc: # typedef int (*initproc)(PyObject *, PyObject *, PyObject *); # # All other functions generated by Argument Clinic are stored in # PyMethodDef structures, in the ml_meth slot, which is of type PyCFunction: # typedef PyObject *(*PyCFunction)(PyObject *, PyObject *); # However! We habitually cast these functions to PyCFunction, # since functions that accept keyword arguments don't fit this signature # but are stored there anyway. So strict type equality isn't important # for these functions. # # So: # # * The name of the first parameter to the impl and the parsing function will always # be self.name. # # * The type of the first parameter to the impl will always be of self.type. # # * If the function is neither tp_new (METHOD_NEW) nor tp_init (METHOD_INIT): # * The type of the first parameter to the parsing function is also self.type. # This means that if you step into the parsing function, your "self" parameter # is of the correct type, which may make debugging more pleasant. # # * Else if the function is tp_new (METHOD_NEW): # * The type of the first parameter to the parsing function is "PyTypeObject *", # so the type signature of the function call is an exact match. # * If self.type != "PyTypeObject *", we cast the first parameter to self.type # in the impl call. # # * Else if the function is tp_init (METHOD_INIT): # * The type of the first parameter to the parsing function is "PyObject *", # so the type signature of the function call is an exact match. # * If self.type != "PyObject *", we cast the first parameter to self.type # in the impl call. @property def parser_type(self) -> str: assert self.type is not None if self.function.kind in {METHOD_INIT, METHOD_NEW, STATIC_METHOD, CLASS_METHOD}: tp, _ = correct_name_for_self(self.function) return tp return self.type def render(self, parameter: Parameter, data: CRenderData) -> None: """ parameter is a clinic.Parameter instance. data is a CRenderData instance. """ if self.function.kind is STATIC_METHOD: return self._render_self(parameter, data) if self.type != self.parser_type: # insert cast to impl_argument[0], aka self. # we know we're in the first slot in all the CRenderData lists, # because we render parameters in order, and self is always first. assert len(data.impl_arguments) == 1 assert data.impl_arguments[0] == self.name assert self.type is not None data.impl_arguments[0] = '(' + self.type + ")" + data.impl_arguments[0] def set_template_dict(self, template_dict: TemplateDict) -> None: template_dict['self_name'] = self.name template_dict['self_type'] = self.parser_type kind = self.function.kind cls = self.function.cls if kind.new_or_init and cls and cls.typedef: if kind is METHOD_NEW: type_check = ( '({0} == base_tp || {0}->tp_init == base_tp->tp_init)' ).format(self.name) else: type_check = ('(Py_IS_TYPE({0}, base_tp) ||\n ' ' Py_TYPE({0})->tp_new == base_tp->tp_new)' ).format(self.name) line = f'{type_check} &&\n ' template_dict['self_type_check'] = line type_object = cls.type_object type_ptr = f'PyTypeObject *base_tp = {type_object};' template_dict['base_type_ptr'] = type_ptr