• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import annotations
2import builtins as bltns
3import functools
4from typing import Any, TypeVar, Literal, TYPE_CHECKING, cast
5from collections.abc import Callable
6
7import libclinic
8from libclinic import fail
9from libclinic import Sentinels, unspecified, unknown
10from libclinic.codegen import CRenderData, Include, TemplateDict
11from libclinic.function import Function, Parameter
12
13
14CConverterClassT = TypeVar("CConverterClassT", bound=type["CConverter"])
15
16
17type_checks = {
18    '&PyLong_Type': ('PyLong_Check', 'int'),
19    '&PyTuple_Type': ('PyTuple_Check', 'tuple'),
20    '&PyList_Type': ('PyList_Check', 'list'),
21    '&PySet_Type': ('PySet_Check', 'set'),
22    '&PyFrozenSet_Type': ('PyFrozenSet_Check', 'frozenset'),
23    '&PyDict_Type': ('PyDict_Check', 'dict'),
24    '&PyUnicode_Type': ('PyUnicode_Check', 'str'),
25    '&PyBytes_Type': ('PyBytes_Check', 'bytes'),
26    '&PyByteArray_Type': ('PyByteArray_Check', 'bytearray'),
27}
28
29
30def add_c_converter(
31        f: CConverterClassT,
32        name: str | None = None
33) -> CConverterClassT:
34    if not name:
35        name = f.__name__
36        if not name.endswith('_converter'):
37            return f
38        name = name.removesuffix('_converter')
39    converters[name] = f
40    return f
41
42
43def add_default_legacy_c_converter(cls: CConverterClassT) -> CConverterClassT:
44    # automatically add converter for default format unit
45    # (but without stomping on the existing one if it's already
46    # set, in case you subclass)
47    if ((cls.format_unit not in ('O&', '')) and
48        (cls.format_unit not in legacy_converters)):
49        legacy_converters[cls.format_unit] = cls
50    return cls
51
52
53class CConverterAutoRegister(type):
54    def __init__(
55        cls, name: str, bases: tuple[type[object], ...], classdict: dict[str, Any]
56    ) -> None:
57        converter_cls = cast(type["CConverter"], cls)
58        add_c_converter(converter_cls)
59        add_default_legacy_c_converter(converter_cls)
60
61class CConverter(metaclass=CConverterAutoRegister):
62    """
63    For the init function, self, name, function, and default
64    must be keyword-or-positional parameters.  All other
65    parameters must be keyword-only.
66    """
67
68    # The C name to use for this variable.
69    name: str
70
71    # The Python name to use for this variable.
72    py_name: str
73
74    # The C type to use for this variable.
75    # 'type' should be a Python string specifying the type, e.g. "int".
76    # If this is a pointer type, the type string should end with ' *'.
77    type: str | None = None
78
79    # The Python default value for this parameter, as a Python value.
80    # Or the magic value "unspecified" if there is no default.
81    # Or the magic value "unknown" if this value is a cannot be evaluated
82    # at Argument-Clinic-preprocessing time (but is presumed to be valid
83    # at runtime).
84    default: object = unspecified
85
86    # If not None, default must be isinstance() of this type.
87    # (You can also specify a tuple of types.)
88    default_type: bltns.type[object] | tuple[bltns.type[object], ...] | None = None
89
90    # "default" converted into a C value, as a string.
91    # Or None if there is no default.
92    c_default: str | None = None
93
94    # "default" converted into a Python value, as a string.
95    # Or None if there is no default.
96    py_default: str | None = None
97
98    # The default value used to initialize the C variable when
99    # there is no default, but not specifying a default may
100    # result in an "uninitialized variable" warning.  This can
101    # easily happen when using option groups--although
102    # properly-written code won't actually use the variable,
103    # the variable does get passed in to the _impl.  (Ah, if
104    # only dataflow analysis could inline the static function!)
105    #
106    # This value is specified as a string.
107    # Every non-abstract subclass should supply a valid value.
108    c_ignored_default: str = 'NULL'
109
110    # If true, wrap with Py_UNUSED.
111    unused = False
112
113    # The C converter *function* to be used, if any.
114    # (If this is not None, format_unit must be 'O&'.)
115    converter: str | None = None
116
117    # Should Argument Clinic add a '&' before the name of
118    # the variable when passing it into the _impl function?
119    impl_by_reference = False
120
121    # Should Argument Clinic add a '&' before the name of
122    # the variable when passing it into PyArg_ParseTuple (AndKeywords)?
123    parse_by_reference = True
124
125    #############################################################
126    #############################################################
127    ## You shouldn't need to read anything below this point to ##
128    ## write your own converter functions.                     ##
129    #############################################################
130    #############################################################
131
132    # The "format unit" to specify for this variable when
133    # parsing arguments using PyArg_ParseTuple (AndKeywords).
134    # Custom converters should always use the default value of 'O&'.
135    format_unit = 'O&'
136
137    # What encoding do we want for this variable?  Only used
138    # by format units starting with 'e'.
139    encoding: str | None = None
140
141    # Should this object be required to be a subclass of a specific type?
142    # If not None, should be a string representing a pointer to a
143    # PyTypeObject (e.g. "&PyUnicode_Type").
144    # Only used by the 'O!' format unit (and the "object" converter).
145    subclass_of: str | None = None
146
147    # See also the 'length_name' property.
148    # Only used by format units ending with '#'.
149    length = False
150
151    # Should we show this parameter in the generated
152    # __text_signature__? This is *almost* always True.
153    # (It's only False for __new__, __init__, and METH_STATIC functions.)
154    show_in_signature = True
155
156    # Overrides the name used in a text signature.
157    # The name used for a "self" parameter must be one of
158    # self, type, or module; however users can set their own.
159    # This lets the self_converter overrule the user-settable
160    # name, *just* for the text signature.
161    # Only set by self_converter.
162    signature_name: str | None = None
163
164    broken_limited_capi: bool = False
165
166    # keep in sync with self_converter.__init__!
167    def __init__(self,
168             # Positional args:
169             name: str,
170             py_name: str,
171             function: Function,
172             default: object = unspecified,
173             *,  # Keyword only args:
174             c_default: str | None = None,
175             py_default: str | None = None,
176             annotation: str | Literal[Sentinels.unspecified] = unspecified,
177             unused: bool = False,
178             **kwargs: Any
179    ) -> None:
180        self.name = libclinic.ensure_legal_c_identifier(name)
181        self.py_name = py_name
182        self.unused = unused
183        self._includes: list[Include] = []
184
185        if default is not unspecified:
186            if (self.default_type
187                and default is not unknown
188                and not isinstance(default, self.default_type)
189            ):
190                if isinstance(self.default_type, type):
191                    types_str = self.default_type.__name__
192                else:
193                    names = [cls.__name__ for cls in self.default_type]
194                    types_str = ', '.join(names)
195                cls_name = self.__class__.__name__
196                fail(f"{cls_name}: default value {default!r} for field "
197                     f"{name!r} is not of type {types_str!r}")
198            self.default = default
199
200        if c_default:
201            self.c_default = c_default
202        if py_default:
203            self.py_default = py_default
204
205        if annotation is not unspecified:
206            fail("The 'annotation' parameter is not currently permitted.")
207
208        # Make sure not to set self.function until after converter_init() has been called.
209        # This prevents you from caching information
210        # about the function in converter_init().
211        # (That breaks if we get cloned.)
212        self.converter_init(**kwargs)
213        self.function = function
214
215    # Add a custom __getattr__ method to improve the error message
216    # if somebody tries to access self.function in converter_init().
217    #
218    # mypy will assume arbitrary access is okay for a class with a __getattr__ method,
219    # and that's not what we want,
220    # so put it inside an `if not TYPE_CHECKING` block
221    if not TYPE_CHECKING:
222        def __getattr__(self, attr):
223            if attr == "function":
224                fail(
225                    f"{self.__class__.__name__!r} object has no attribute 'function'.\n"
226                    f"Note: accessing self.function inside converter_init is disallowed!"
227                )
228            return super().__getattr__(attr)
229    # this branch is just here for coverage reporting
230    else:  # pragma: no cover
231        pass
232
233    def converter_init(self) -> None:
234        pass
235
236    def is_optional(self) -> bool:
237        return (self.default is not unspecified)
238
239    def _render_self(self, parameter: Parameter, data: CRenderData) -> None:
240        self.parameter = parameter
241        name = self.parser_name
242
243        # impl_arguments
244        s = ("&" if self.impl_by_reference else "") + name
245        data.impl_arguments.append(s)
246        if self.length:
247            data.impl_arguments.append(self.length_name)
248
249        # impl_parameters
250        data.impl_parameters.append(self.simple_declaration(by_reference=self.impl_by_reference))
251        if self.length:
252            data.impl_parameters.append(f"Py_ssize_t {self.length_name}")
253
254    def _render_non_self(
255            self,
256            parameter: Parameter,
257            data: CRenderData
258    ) -> None:
259        self.parameter = parameter
260        name = self.name
261
262        # declarations
263        d = self.declaration(in_parser=True)
264        data.declarations.append(d)
265
266        # initializers
267        initializers = self.initialize()
268        if initializers:
269            data.initializers.append('/* initializers for ' + name + ' */\n' + initializers.rstrip())
270
271        # modifications
272        modifications = self.modify()
273        if modifications:
274            data.modifications.append('/* modifications for ' + name + ' */\n' + modifications.rstrip())
275
276        # keywords
277        if parameter.is_vararg():
278            pass
279        elif parameter.is_positional_only():
280            data.keywords.append('')
281        else:
282            data.keywords.append(parameter.name)
283
284        # format_units
285        if self.is_optional() and '|' not in data.format_units:
286            data.format_units.append('|')
287        if parameter.is_keyword_only() and '$' not in data.format_units:
288            data.format_units.append('$')
289        data.format_units.append(self.format_unit)
290
291        # parse_arguments
292        self.parse_argument(data.parse_arguments)
293
294        # post_parsing
295        if post_parsing := self.post_parsing():
296            data.post_parsing.append('/* Post parse cleanup for ' + name + ' */\n' + post_parsing.rstrip() + '\n')
297
298        # cleanup
299        cleanup = self.cleanup()
300        if cleanup:
301            data.cleanup.append('/* Cleanup for ' + name + ' */\n' + cleanup.rstrip() + "\n")
302
303    def render(self, parameter: Parameter, data: CRenderData) -> None:
304        """
305        parameter is a clinic.Parameter instance.
306        data is a CRenderData instance.
307        """
308        self._render_self(parameter, data)
309        self._render_non_self(parameter, data)
310
311    @functools.cached_property
312    def length_name(self) -> str:
313        """Computes the name of the associated "length" variable."""
314        assert self.length is not None
315        return self.parser_name + "_length"
316
317    # Why is this one broken out separately?
318    # For "positional-only" function parsing,
319    # which generates a bunch of PyArg_ParseTuple calls.
320    def parse_argument(self, args: list[str]) -> None:
321        assert not (self.converter and self.encoding)
322        if self.format_unit == 'O&':
323            assert self.converter
324            args.append(self.converter)
325
326        if self.encoding:
327            args.append(libclinic.c_repr(self.encoding))
328        elif self.subclass_of:
329            args.append(self.subclass_of)
330
331        s = ("&" if self.parse_by_reference else "") + self.parser_name
332        args.append(s)
333
334        if self.length:
335            args.append(f"&{self.length_name}")
336
337    #
338    # All the functions after here are intended as extension points.
339    #
340
341    def simple_declaration(
342            self,
343            by_reference: bool = False,
344            *,
345            in_parser: bool = False
346    ) -> str:
347        """
348        Computes the basic declaration of the variable.
349        Used in computing the prototype declaration and the
350        variable declaration.
351        """
352        assert isinstance(self.type, str)
353        prototype = [self.type]
354        if by_reference or not self.type.endswith('*'):
355            prototype.append(" ")
356        if by_reference:
357            prototype.append('*')
358        if in_parser:
359            name = self.parser_name
360        else:
361            name = self.name
362            if self.unused:
363                name = f"Py_UNUSED({name})"
364        prototype.append(name)
365        return "".join(prototype)
366
367    def declaration(self, *, in_parser: bool = False) -> str:
368        """
369        The C statement to declare this variable.
370        """
371        declaration = [self.simple_declaration(in_parser=True)]
372        default = self.c_default
373        if not default and self.parameter.group:
374            default = self.c_ignored_default
375        if default:
376            declaration.append(" = ")
377            declaration.append(default)
378        declaration.append(";")
379        if self.length:
380            declaration.append('\n')
381            declaration.append(f"Py_ssize_t {self.length_name};")
382        return "".join(declaration)
383
384    def initialize(self) -> str:
385        """
386        The C statements required to set up this variable before parsing.
387        Returns a string containing this code indented at column 0.
388        If no initialization is necessary, returns an empty string.
389        """
390        return ""
391
392    def modify(self) -> str:
393        """
394        The C statements required to modify this variable after parsing.
395        Returns a string containing this code indented at column 0.
396        If no modification is necessary, returns an empty string.
397        """
398        return ""
399
400    def post_parsing(self) -> str:
401        """
402        The C statements required to do some operations after the end of parsing but before cleaning up.
403        Return a string containing this code indented at column 0.
404        If no operation is necessary, return an empty string.
405        """
406        return ""
407
408    def cleanup(self) -> str:
409        """
410        The C statements required to clean up after this variable.
411        Returns a string containing this code indented at column 0.
412        If no cleanup is necessary, returns an empty string.
413        """
414        return ""
415
416    def pre_render(self) -> None:
417        """
418        A second initialization function, like converter_init,
419        called just before rendering.
420        You are permitted to examine self.function here.
421        """
422        pass
423
424    def bad_argument(self, displayname: str, expected: str, *, limited_capi: bool, expected_literal: bool = True) -> str:
425        assert '"' not in expected
426        if limited_capi:
427            if expected_literal:
428                return (f'PyErr_Format(PyExc_TypeError, '
429                        f'"{{{{name}}}}() {displayname} must be {expected}, not %T", '
430                        f'{{argname}});')
431            else:
432                return (f'PyErr_Format(PyExc_TypeError, '
433                        f'"{{{{name}}}}() {displayname} must be %s, not %T", '
434                        f'"{expected}", {{argname}});')
435        else:
436            if expected_literal:
437                expected = f'"{expected}"'
438            self.add_include('pycore_modsupport.h', '_PyArg_BadArgument()')
439            return f'_PyArg_BadArgument("{{{{name}}}}", "{displayname}", {expected}, {{argname}});'
440
441    def format_code(self, fmt: str, *,
442                    argname: str,
443                    bad_argument: str | None = None,
444                    bad_argument2: str | None = None,
445                    **kwargs: Any) -> str:
446        if '{bad_argument}' in fmt:
447            if not bad_argument:
448                raise TypeError("required 'bad_argument' argument")
449            fmt = fmt.replace('{bad_argument}', bad_argument)
450        if '{bad_argument2}' in fmt:
451            if not bad_argument2:
452                raise TypeError("required 'bad_argument2' argument")
453            fmt = fmt.replace('{bad_argument2}', bad_argument2)
454        return fmt.format(argname=argname, paramname=self.parser_name, **kwargs)
455
456    def use_converter(self) -> None:
457        """Method called when self.converter is used to parse an argument."""
458        pass
459
460    def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None:
461        if self.format_unit == 'O&':
462            self.use_converter()
463            return self.format_code("""
464                if (!{converter}({argname}, &{paramname})) {{{{
465                    goto exit;
466                }}}}
467                """,
468                argname=argname,
469                converter=self.converter)
470        if self.format_unit == 'O!':
471            cast = '(%s)' % self.type if self.type != 'PyObject *' else ''
472            if self.subclass_of in type_checks:
473                typecheck, typename = type_checks[self.subclass_of]
474                return self.format_code("""
475                    if (!{typecheck}({argname})) {{{{
476                        {bad_argument}
477                        goto exit;
478                    }}}}
479                    {paramname} = {cast}{argname};
480                    """,
481                    argname=argname,
482                    bad_argument=self.bad_argument(displayname, typename, limited_capi=limited_capi),
483                    typecheck=typecheck, typename=typename, cast=cast)
484            return self.format_code("""
485                if (!PyObject_TypeCheck({argname}, {subclass_of})) {{{{
486                    {bad_argument}
487                    goto exit;
488                }}}}
489                {paramname} = {cast}{argname};
490                """,
491                argname=argname,
492                bad_argument=self.bad_argument(displayname, '({subclass_of})->tp_name',
493                                               expected_literal=False, limited_capi=limited_capi),
494                subclass_of=self.subclass_of, cast=cast)
495        if self.format_unit == 'O':
496            cast = '(%s)' % self.type if self.type != 'PyObject *' else ''
497            return self.format_code("""
498                {paramname} = {cast}{argname};
499                """,
500                argname=argname, cast=cast)
501        return None
502
503    def set_template_dict(self, template_dict: TemplateDict) -> None:
504        pass
505
506    @property
507    def parser_name(self) -> str:
508        if self.name in libclinic.CLINIC_PREFIXED_ARGS: # bpo-39741
509            return libclinic.CLINIC_PREFIX + self.name
510        else:
511            return self.name
512
513    def add_include(self, name: str, reason: str,
514                    *, condition: str | None = None) -> None:
515        include = Include(name, reason, condition)
516        self._includes.append(include)
517
518    def get_includes(self) -> list[Include]:
519        return self._includes
520
521
522ConverterType = Callable[..., CConverter]
523ConverterDict = dict[str, ConverterType]
524
525# maps strings to callables.
526# these callables must be of the form:
527#   def foo(name, default, *, ...)
528# The callable may have any number of keyword-only parameters.
529# The callable must return a CConverter object.
530# The callable should not call builtins.print.
531converters: ConverterDict = {}
532
533# maps strings to callables.
534# these callables follow the same rules as those for "converters" above.
535# note however that they will never be called with keyword-only parameters.
536legacy_converters: ConverterDict = {}
537
538
539def add_legacy_c_converter(
540    format_unit: str,
541    **kwargs: Any
542) -> Callable[[CConverterClassT], CConverterClassT]:
543    def closure(f: CConverterClassT) -> CConverterClassT:
544        added_f: Callable[..., CConverter]
545        if not kwargs:
546            added_f = f
547        else:
548            added_f = functools.partial(f, **kwargs)
549        if format_unit:
550            legacy_converters[format_unit] = added_f
551        return f
552    return closure
553