summaryrefslogtreecommitdiffstats
path: root/Tools/clinic
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2024-04-02 10:09:53 (GMT)
committerGitHub <noreply@github.com>2024-04-02 10:09:53 (GMT)
commit5fd1897ec51cb64ef7990ada538fcd8d9ca1f74b (patch)
tree957892a83948f09e0c094d1714318ce412921977 /Tools/clinic
parent9dae05ee59eeba0e67af2a46f2a2907c9f8d7e4a (diff)
downloadcpython-5fd1897ec51cb64ef7990ada538fcd8d9ca1f74b.zip
cpython-5fd1897ec51cb64ef7990ada538fcd8d9ca1f74b.tar.gz
cpython-5fd1897ec51cb64ef7990ada538fcd8d9ca1f74b.tar.bz2
gh-113317: Argument Clinic: Add libclinic.converters module (#117315)
Move the following converter classes to libclinic.converters: * PyByteArrayObject_converter * PyBytesObject_converter * Py_UNICODE_converter * Py_buffer_converter * Py_complex_converter * Py_ssize_t_converter * bool_converter * byte_converter * char_converter * defining_class_converter * double_converter * fildes_converter * float_converter * int_converter * long_converter * long_long_converter * object_converter * self_converter * short_converter * size_t_converter * slice_index_converter * str_converter * unicode_converter * unsigned_char_converter * unsigned_int_converter * unsigned_long_converter * unsigned_long_long_converter * unsigned_short_converter Move also the following classes to libclinic.converters: * buffer * robuffer * rwbuffer Move the following functions to libclinic.converters: * correct_name_for_self() * r() * str_converter_key() Move Null and NULL to libclinic.utils.
Diffstat (limited to 'Tools/clinic')
-rwxr-xr-xTools/clinic/clinic.py1208
-rw-r--r--Tools/clinic/libclinic/__init__.py20
-rw-r--r--Tools/clinic/libclinic/converter.py16
-rw-r--r--Tools/clinic/libclinic/converters.py1211
-rw-r--r--Tools/clinic/libclinic/function.py3
-rw-r--r--Tools/clinic/libclinic/utils.py9
6 files changed, 1255 insertions, 1212 deletions
diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py
index ea480e6..a4e004d 100755
--- a/Tools/clinic/clinic.py
+++ b/Tools/clinic/clinic.py
@@ -8,7 +8,6 @@ from __future__ import annotations
import argparse
import ast
-import builtins as bltns
import contextlib
import dataclasses as dc
import enum
@@ -46,7 +45,7 @@ import libclinic
import libclinic.cpp
from libclinic import (
ClinicError, Sentinels, VersionTuple,
- fail, warn, unspecified, unknown)
+ fail, warn, unspecified, unknown, NULL)
from libclinic.function import (
Module, Class, Function, Parameter,
ClassDict, ModuleDict, FunctionKind,
@@ -56,8 +55,11 @@ from libclinic.language import Language, PythonLanguage
from libclinic.block_parser import Block, BlockParser
from libclinic.crenderdata import CRenderData, Include, TemplateDict
from libclinic.converter import (
- CConverter, CConverterClassT, ConverterType,
+ CConverter, ConverterType,
converters, legacy_converters)
+from libclinic.converters import (
+ self_converter, defining_class_converter, object_converter, buffer,
+ robuffer, rwbuffer, correct_name_for_self)
# TODO:
@@ -77,15 +79,6 @@ from libclinic.converter import (
LIMITED_CAPI_REGEX = re.compile(r'# *define +Py_LIMITED_API')
-# This one needs to be a distinct class, unlike the other two
-class Null:
- def __repr__(self) -> str:
- return '<Null>'
-
-
-NULL = Null()
-
-
ParamTuple = tuple["Parameter", ...]
@@ -2098,24 +2091,6 @@ __xor__
ReturnConverterType = Callable[..., "CReturnConverter"]
-def add_legacy_c_converter(
- format_unit: str,
- **kwargs: Any
-) -> Callable[[CConverterClassT], CConverterClassT]:
- """
- Adds a legacy converter.
- """
- def closure(f: CConverterClassT) -> CConverterClassT:
- added_f: Callable[..., CConverter]
- if not kwargs:
- added_f = f
- else:
- added_f = functools.partial(f, **kwargs)
- if format_unit:
- legacy_converters[format_unit] = added_f
- return f
- return closure
-
# maps strings to callables.
# these callables must be of the form:
# def foo(*, ...)
@@ -2125,1179 +2100,6 @@ def add_legacy_c_converter(
ReturnConverterDict = dict[str, ReturnConverterType]
return_converters: ReturnConverterDict = {}
-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
-
def add_c_return_converter(
f: ReturnConverterType,
diff --git a/Tools/clinic/libclinic/__init__.py b/Tools/clinic/libclinic/__init__.py
index 32231b8..7c5cede 100644
--- a/Tools/clinic/libclinic/__init__.py
+++ b/Tools/clinic/libclinic/__init__.py
@@ -25,13 +25,15 @@ from .identifiers import (
)
from .utils import (
FormatCounterFormatter,
+ NULL,
+ Null,
+ Sentinels,
+ VersionTuple,
compute_checksum,
create_regex,
- write_file,
- VersionTuple,
- Sentinels,
- unspecified,
unknown,
+ unspecified,
+ write_file,
)
@@ -61,13 +63,15 @@ __all__ = [
# Utility functions
"FormatCounterFormatter",
+ "NULL",
+ "Null",
+ "Sentinels",
+ "VersionTuple",
"compute_checksum",
"create_regex",
- "write_file",
- "VersionTuple",
- "Sentinels",
- "unspecified",
"unknown",
+ "unspecified",
+ "write_file",
]
diff --git a/Tools/clinic/libclinic/converter.py b/Tools/clinic/libclinic/converter.py
index 744d03c..ac78be3 100644
--- a/Tools/clinic/libclinic/converter.py
+++ b/Tools/clinic/libclinic/converter.py
@@ -531,3 +531,19 @@ converters: ConverterDict = {}
# these callables follow the same rules as those for "converters" above.
# note however that they will never be called with keyword-only parameters.
legacy_converters: ConverterDict = {}
+
+
+def add_legacy_c_converter(
+ format_unit: str,
+ **kwargs: Any
+) -> Callable[[CConverterClassT], CConverterClassT]:
+ def closure(f: CConverterClassT) -> CConverterClassT:
+ added_f: Callable[..., CConverter]
+ if not kwargs:
+ added_f = f
+ else:
+ added_f = functools.partial(f, **kwargs)
+ if format_unit:
+ legacy_converters[format_unit] = added_f
+ return f
+ return closure
diff --git a/Tools/clinic/libclinic/converters.py b/Tools/clinic/libclinic/converters.py
new file mode 100644
index 0000000..7fc16f1
--- /dev/null
+++ b/Tools/clinic/libclinic/converters.py
@@ -0,0 +1,1211 @@
+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.crenderdata 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
diff --git a/Tools/clinic/libclinic/function.py b/Tools/clinic/libclinic/function.py
index 4fafedb..b0dd084 100644
--- a/Tools/clinic/libclinic/function.py
+++ b/Tools/clinic/libclinic/function.py
@@ -6,8 +6,9 @@ import functools
import inspect
from typing import Final, Any, TYPE_CHECKING
if TYPE_CHECKING:
- from clinic import Clinic, CReturnConverter, self_converter
+ from clinic import Clinic, CReturnConverter
from libclinic.converter import CConverter
+ from libclinic.converters import self_converter
from libclinic import VersionTuple, unspecified
diff --git a/Tools/clinic/libclinic/utils.py b/Tools/clinic/libclinic/utils.py
index 95a69f7..17e8f35 100644
--- a/Tools/clinic/libclinic/utils.py
+++ b/Tools/clinic/libclinic/utils.py
@@ -82,3 +82,12 @@ class Sentinels(enum.Enum):
unspecified: Final = Sentinels.unspecified
unknown: Final = Sentinels.unknown
+
+
+# This one needs to be a distinct class, unlike the other two
+class Null:
+ def __repr__(self) -> str:
+ return '<Null>'
+
+
+NULL = Null()