• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import annotations
2from typing import TYPE_CHECKING, Final
3
4import libclinic
5from libclinic import fail, warn
6from libclinic.function import (
7    Function, Parameter,
8    GETTER, SETTER, METHOD_NEW)
9from libclinic.converter import CConverter
10from libclinic.converters import (
11    defining_class_converter, object_converter, self_converter)
12if TYPE_CHECKING:
13    from libclinic.clanguage import CLanguage
14    from libclinic.codegen import CodeGen
15
16
17def declare_parser(
18    f: Function,
19    *,
20    hasformat: bool = False,
21    codegen: CodeGen,
22) -> str:
23    """
24    Generates the code template for a static local PyArg_Parser variable,
25    with an initializer.  For core code (incl. builtin modules) the
26    kwtuple field is also statically initialized.  Otherwise
27    it is initialized at runtime.
28    """
29    limited_capi = codegen.limited_capi
30    if hasformat:
31        fname = ''
32        format_ = '.format = "{format_units}:{name}",'
33    else:
34        fname = '.fname = "{name}",'
35        format_ = ''
36
37    num_keywords = len([
38        p for p in f.parameters.values()
39        if not p.is_positional_only() and not p.is_vararg()
40    ])
41
42    condition = '#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)'
43    if limited_capi:
44        declarations = """
45            #define KWTUPLE NULL
46        """
47    elif num_keywords == 0:
48        declarations = """
49            #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
50            #  define KWTUPLE (PyObject *)&_Py_SINGLETON(tuple_empty)
51            #else
52            #  define KWTUPLE NULL
53            #endif
54        """
55
56        codegen.add_include('pycore_runtime.h', '_Py_SINGLETON()',
57                            condition=condition)
58    else:
59        # XXX Why do we not statically allocate the tuple
60        # for non-builtin modules?
61        declarations = """
62            #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
63
64            #define NUM_KEYWORDS %d
65            static struct {{
66                PyGC_Head _this_is_not_used;
67                PyObject_VAR_HEAD
68                PyObject *ob_item[NUM_KEYWORDS];
69            }} _kwtuple = {{
70                .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
71                .ob_item = {{ {keywords_py} }},
72            }};
73            #undef NUM_KEYWORDS
74            #define KWTUPLE (&_kwtuple.ob_base.ob_base)
75
76            #else  // !Py_BUILD_CORE
77            #  define KWTUPLE NULL
78            #endif  // !Py_BUILD_CORE
79        """ % num_keywords
80
81        codegen.add_include('pycore_gc.h', 'PyGC_Head',
82                            condition=condition)
83        codegen.add_include('pycore_runtime.h', '_Py_ID()',
84                            condition=condition)
85
86    declarations += """
87            static const char * const _keywords[] = {{{keywords_c} NULL}};
88            static _PyArg_Parser _parser = {{
89                .keywords = _keywords,
90                %s
91                .kwtuple = KWTUPLE,
92            }};
93            #undef KWTUPLE
94    """ % (format_ or fname)
95    return libclinic.normalize_snippet(declarations)
96
97
98NO_VARARG: Final[str] = "PY_SSIZE_T_MAX"
99PARSER_PROTOTYPE_KEYWORD: Final[str] = libclinic.normalize_snippet("""
100    static PyObject *
101    {c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs)
102""")
103PARSER_PROTOTYPE_KEYWORD___INIT__: Final[str] = libclinic.normalize_snippet("""
104    static int
105    {c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs)
106""")
107PARSER_PROTOTYPE_VARARGS: Final[str] = libclinic.normalize_snippet("""
108    static PyObject *
109    {c_basename}({self_type}{self_name}, PyObject *args)
110""")
111PARSER_PROTOTYPE_FASTCALL: Final[str] = libclinic.normalize_snippet("""
112    static PyObject *
113    {c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs)
114""")
115PARSER_PROTOTYPE_FASTCALL_KEYWORDS: Final[str] = libclinic.normalize_snippet("""
116    static PyObject *
117    {c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
118""")
119PARSER_PROTOTYPE_DEF_CLASS: Final[str] = libclinic.normalize_snippet("""
120    static PyObject *
121    {c_basename}({self_type}{self_name}, PyTypeObject *{defining_class_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
122""")
123PARSER_PROTOTYPE_NOARGS: Final[str] = libclinic.normalize_snippet("""
124    static PyObject *
125    {c_basename}({self_type}{self_name}, PyObject *Py_UNUSED(ignored))
126""")
127PARSER_PROTOTYPE_GETTER: Final[str] = libclinic.normalize_snippet("""
128    static PyObject *
129    {c_basename}({self_type}{self_name}, void *Py_UNUSED(context))
130""")
131PARSER_PROTOTYPE_SETTER: Final[str] = libclinic.normalize_snippet("""
132    static int
133    {c_basename}({self_type}{self_name}, PyObject *value, void *Py_UNUSED(context))
134""")
135METH_O_PROTOTYPE: Final[str] = libclinic.normalize_snippet("""
136    static PyObject *
137    {c_basename}({impl_parameters})
138""")
139DOCSTRING_PROTOTYPE_VAR: Final[str] = libclinic.normalize_snippet("""
140    PyDoc_VAR({c_basename}__doc__);
141""")
142DOCSTRING_PROTOTYPE_STRVAR: Final[str] = libclinic.normalize_snippet("""
143    PyDoc_STRVAR({c_basename}__doc__,
144    {docstring});
145""")
146GETSET_DOCSTRING_PROTOTYPE_STRVAR: Final[str] = libclinic.normalize_snippet("""
147    PyDoc_STRVAR({getset_basename}__doc__,
148    {docstring});
149    #define {getset_basename}_DOCSTR {getset_basename}__doc__
150""")
151IMPL_DEFINITION_PROTOTYPE: Final[str] = libclinic.normalize_snippet("""
152    static {impl_return_type}
153    {c_basename}_impl({impl_parameters})
154""")
155METHODDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
156    #define {methoddef_name}    \
157        {{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}},
158""")
159GETTERDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
160    #if !defined({getset_basename}_DOCSTR)
161    #  define {getset_basename}_DOCSTR NULL
162    #endif
163    #if defined({getset_name}_GETSETDEF)
164    #  undef {getset_name}_GETSETDEF
165    #  define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, {getset_basename}_DOCSTR}},
166    #else
167    #  define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, NULL, {getset_basename}_DOCSTR}},
168    #endif
169""")
170SETTERDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r"""
171    #if !defined({getset_basename}_DOCSTR)
172    #  define {getset_basename}_DOCSTR NULL
173    #endif
174    #if defined({getset_name}_GETSETDEF)
175    #  undef {getset_name}_GETSETDEF
176    #  define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, {getset_basename}_DOCSTR}},
177    #else
178    #  define {getset_name}_GETSETDEF {{"{name}", NULL, (setter){getset_basename}_set, NULL}},
179    #endif
180""")
181METHODDEF_PROTOTYPE_IFNDEF: Final[str] = libclinic.normalize_snippet("""
182    #ifndef {methoddef_name}
183        #define {methoddef_name}
184    #endif /* !defined({methoddef_name}) */
185""")
186
187
188class ParseArgsCodeGen:
189    func: Function
190    codegen: CodeGen
191    limited_capi: bool = False
192
193    # Function parameters
194    parameters: list[Parameter]
195    converters: list[CConverter]
196
197    # Is 'defining_class' used for the first parameter?
198    requires_defining_class: bool
199
200    # Use METH_FASTCALL calling convention?
201    fastcall: bool
202
203    # Declaration of the return variable (ex: "int return_value;")
204    return_value_declaration: str
205
206    # Calling convention (ex: "METH_NOARGS")
207    flags: str
208
209    # Variables declarations
210    declarations: str
211
212    pos_only: int = 0
213    min_pos: int = 0
214    max_pos: int = 0
215    min_kw_only: int = 0
216    pseudo_args: int = 0
217    vararg: int | str = NO_VARARG
218
219    docstring_prototype: str
220    docstring_definition: str
221    impl_prototype: str | None
222    impl_definition: str
223    methoddef_define: str
224    parser_prototype: str
225    parser_definition: str
226    cpp_if: str
227    cpp_endif: str
228    methoddef_ifndef: str
229
230    parser_body_fields: tuple[str, ...]
231
232    def __init__(self, func: Function, codegen: CodeGen) -> None:
233        self.func = func
234        self.codegen = codegen
235
236        self.parameters = list(self.func.parameters.values())
237        first_param = self.parameters.pop(0)
238        if not isinstance(first_param.converter, self_converter):
239            raise ValueError("the first parameter must use self_converter")
240
241        self.requires_defining_class = False
242        if self.parameters and isinstance(self.parameters[0].converter, defining_class_converter):
243            self.requires_defining_class = True
244            del self.parameters[0]
245        self.converters = [p.converter for p in self.parameters]
246
247        if self.func.critical_section:
248            self.codegen.add_include('pycore_critical_section.h',
249                                     'Py_BEGIN_CRITICAL_SECTION()')
250        self.fastcall = not self.is_new_or_init()
251
252        self.pos_only = 0
253        self.min_pos = 0
254        self.max_pos = 0
255        self.min_kw_only = 0
256        self.pseudo_args = 0
257        for i, p in enumerate(self.parameters, 1):
258            if p.is_keyword_only():
259                assert not p.is_positional_only()
260                if not p.is_optional():
261                    self.min_kw_only = i - self.max_pos - int(self.vararg != NO_VARARG)
262            elif p.is_vararg():
263                self.pseudo_args += 1
264                self.vararg = i - 1
265            else:
266                if self.vararg == NO_VARARG:
267                    self.max_pos = i
268                if p.is_positional_only():
269                    self.pos_only = i
270                if not p.is_optional():
271                    self.min_pos = i
272
273    def is_new_or_init(self) -> bool:
274        return self.func.kind.new_or_init
275
276    def has_option_groups(self) -> bool:
277        return (bool(self.parameters
278                and (self.parameters[0].group or self.parameters[-1].group)))
279
280    def use_meth_o(self) -> bool:
281        return (len(self.parameters) == 1
282                and self.parameters[0].is_positional_only()
283                and not self.converters[0].is_optional()
284                and not self.requires_defining_class
285                and not self.is_new_or_init())
286
287    def use_simple_return(self) -> bool:
288        return (self.func.return_converter.type == 'PyObject *'
289                and not self.func.critical_section)
290
291    def select_prototypes(self) -> None:
292        self.docstring_prototype = ''
293        self.docstring_definition = ''
294        self.methoddef_define = METHODDEF_PROTOTYPE_DEFINE
295        self.return_value_declaration = "PyObject *return_value = NULL;"
296
297        if self.is_new_or_init() and not self.func.docstring:
298            pass
299        elif self.func.kind is GETTER:
300            self.methoddef_define = GETTERDEF_PROTOTYPE_DEFINE
301            if self.func.docstring:
302                self.docstring_definition = GETSET_DOCSTRING_PROTOTYPE_STRVAR
303        elif self.func.kind is SETTER:
304            if self.func.docstring:
305                fail("docstrings are only supported for @getter, not @setter")
306            self.return_value_declaration = "int {return_value};"
307            self.methoddef_define = SETTERDEF_PROTOTYPE_DEFINE
308        else:
309            self.docstring_prototype = DOCSTRING_PROTOTYPE_VAR
310            self.docstring_definition = DOCSTRING_PROTOTYPE_STRVAR
311
312    def init_limited_capi(self) -> None:
313        self.limited_capi = self.codegen.limited_capi
314        if self.limited_capi and (self.pseudo_args or
315                (any(p.is_optional() for p in self.parameters) and
316                 any(p.is_keyword_only() and not p.is_optional() for p in self.parameters)) or
317                any(c.broken_limited_capi for c in self.converters)):
318            warn(f"Function {self.func.full_name} cannot use limited C API")
319            self.limited_capi = False
320
321    def parser_body(
322        self,
323        *fields: str,
324        declarations: str = ''
325    ) -> None:
326        lines = [self.parser_prototype]
327        self.parser_body_fields = fields
328
329        preamble = libclinic.normalize_snippet("""
330            {{
331                {return_value_declaration}
332                {parser_declarations}
333                {declarations}
334                {initializers}
335        """) + "\n"
336        finale = libclinic.normalize_snippet("""
337                {modifications}
338                {lock}
339                {return_value} = {c_basename}_impl({impl_arguments});
340                {unlock}
341                {return_conversion}
342                {post_parsing}
343
344            {exit_label}
345                {cleanup}
346                return return_value;
347            }}
348        """)
349        for field in preamble, *fields, finale:
350            lines.append(field)
351        code = libclinic.linear_format("\n".join(lines),
352                                       parser_declarations=self.declarations)
353        self.parser_definition = code
354
355    def parse_no_args(self) -> None:
356        parser_code: list[str] | None
357        simple_return = self.use_simple_return()
358        if self.func.kind is GETTER:
359            self.parser_prototype = PARSER_PROTOTYPE_GETTER
360            parser_code = []
361        elif self.func.kind is SETTER:
362            self.parser_prototype = PARSER_PROTOTYPE_SETTER
363            parser_code = []
364        elif not self.requires_defining_class:
365            # no self.parameters, METH_NOARGS
366            self.flags = "METH_NOARGS"
367            self.parser_prototype = PARSER_PROTOTYPE_NOARGS
368            parser_code = []
369        else:
370            assert self.fastcall
371
372            self.flags = "METH_METHOD|METH_FASTCALL|METH_KEYWORDS"
373            self.parser_prototype = PARSER_PROTOTYPE_DEF_CLASS
374            return_error = ('return NULL;' if simple_return
375                            else 'goto exit;')
376            parser_code = [libclinic.normalize_snippet("""
377                if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {{
378                    PyErr_SetString(PyExc_TypeError, "{name}() takes no arguments");
379                    %s
380                }}
381                """ % return_error, indent=4)]
382
383        if simple_return:
384            self.parser_definition = '\n'.join([
385                self.parser_prototype,
386                '{{',
387                *parser_code,
388                '    return {c_basename}_impl({impl_arguments});',
389                '}}'])
390        else:
391            self.parser_body(*parser_code)
392
393    def parse_one_arg(self) -> None:
394        self.flags = "METH_O"
395
396        if (isinstance(self.converters[0], object_converter) and
397            self.converters[0].format_unit == 'O'):
398            meth_o_prototype = METH_O_PROTOTYPE
399
400            if self.use_simple_return():
401                # maps perfectly to METH_O, doesn't need a return converter.
402                # so we skip making a parse function
403                # and call directly into the impl function.
404                self.impl_prototype = ''
405                self.impl_definition = meth_o_prototype
406            else:
407                # SLIGHT HACK
408                # use impl_parameters for the parser here!
409                self.parser_prototype = meth_o_prototype
410                self.parser_body()
411
412        else:
413            argname = 'arg'
414            if self.parameters[0].name == argname:
415                argname += '_'
416            self.parser_prototype = libclinic.normalize_snippet("""
417                static PyObject *
418                {c_basename}({self_type}{self_name}, PyObject *%s)
419                """ % argname)
420
421            displayname = self.parameters[0].get_displayname(0)
422            parsearg: str | None
423            parsearg = self.converters[0].parse_arg(argname, displayname,
424                                                    limited_capi=self.limited_capi)
425            if parsearg is None:
426                self.converters[0].use_converter()
427                parsearg = """
428                    if (!PyArg_Parse(%s, "{format_units}:{name}", {parse_arguments})) {{
429                        goto exit;
430                    }}
431                    """ % argname
432
433            parser_code = libclinic.normalize_snippet(parsearg, indent=4)
434            self.parser_body(parser_code)
435
436    def parse_option_groups(self) -> None:
437        # positional parameters with option groups
438        # (we have to generate lots of PyArg_ParseTuple calls
439        #  in a big switch statement)
440
441        self.flags = "METH_VARARGS"
442        self.parser_prototype = PARSER_PROTOTYPE_VARARGS
443        parser_code = '    {option_group_parsing}'
444        self.parser_body(parser_code)
445
446    def parse_pos_only(self) -> None:
447        if self.fastcall:
448            # positional-only, but no option groups
449            # we only need one call to _PyArg_ParseStack
450
451            self.flags = "METH_FASTCALL"
452            self.parser_prototype = PARSER_PROTOTYPE_FASTCALL
453            nargs = 'nargs'
454            argname_fmt = 'args[%d]'
455        else:
456            # positional-only, but no option groups
457            # we only need one call to PyArg_ParseTuple
458
459            self.flags = "METH_VARARGS"
460            self.parser_prototype = PARSER_PROTOTYPE_VARARGS
461            if self.limited_capi:
462                nargs = 'PyTuple_Size(args)'
463                argname_fmt = 'PyTuple_GetItem(args, %d)'
464            else:
465                nargs = 'PyTuple_GET_SIZE(args)'
466                argname_fmt = 'PyTuple_GET_ITEM(args, %d)'
467
468        left_args = f"{nargs} - {self.max_pos}"
469        max_args = NO_VARARG if (self.vararg != NO_VARARG) else self.max_pos
470        if self.limited_capi:
471            parser_code = []
472            if nargs != 'nargs':
473                nargs_def = f'Py_ssize_t nargs = {nargs};'
474                parser_code.append(libclinic.normalize_snippet(nargs_def, indent=4))
475                nargs = 'nargs'
476            if self.min_pos == max_args:
477                pl = '' if self.min_pos == 1 else 's'
478                parser_code.append(libclinic.normalize_snippet(f"""
479                    if ({nargs} != {self.min_pos}) {{{{
480                        PyErr_Format(PyExc_TypeError, "{{name}} expected {self.min_pos} argument{pl}, got %zd", {nargs});
481                        goto exit;
482                    }}}}
483                    """,
484                indent=4))
485            else:
486                if self.min_pos:
487                    pl = '' if self.min_pos == 1 else 's'
488                    parser_code.append(libclinic.normalize_snippet(f"""
489                        if ({nargs} < {self.min_pos}) {{{{
490                            PyErr_Format(PyExc_TypeError, "{{name}} expected at least {self.min_pos} argument{pl}, got %zd", {nargs});
491                            goto exit;
492                        }}}}
493                        """,
494                        indent=4))
495                if max_args != NO_VARARG:
496                    pl = '' if max_args == 1 else 's'
497                    parser_code.append(libclinic.normalize_snippet(f"""
498                        if ({nargs} > {max_args}) {{{{
499                            PyErr_Format(PyExc_TypeError, "{{name}} expected at most {max_args} argument{pl}, got %zd", {nargs});
500                            goto exit;
501                        }}}}
502                        """,
503                    indent=4))
504        else:
505            self.codegen.add_include('pycore_modsupport.h',
506                                     '_PyArg_CheckPositional()')
507            parser_code = [libclinic.normalize_snippet(f"""
508                if (!_PyArg_CheckPositional("{{name}}", {nargs}, {self.min_pos}, {max_args})) {{{{
509                    goto exit;
510                }}}}
511                """, indent=4)]
512
513        has_optional = False
514        use_parser_code = True
515        for i, p in enumerate(self.parameters):
516            if p.is_vararg():
517                if self.fastcall:
518                    parser_code.append(libclinic.normalize_snippet("""
519                        %s = PyTuple_New(%s);
520                        if (!%s) {{
521                            goto exit;
522                        }}
523                        for (Py_ssize_t i = 0; i < %s; ++i) {{
524                            PyTuple_SET_ITEM(%s, i, Py_NewRef(args[%d + i]));
525                        }}
526                        """ % (
527                            p.converter.parser_name,
528                            left_args,
529                            p.converter.parser_name,
530                            left_args,
531                            p.converter.parser_name,
532                            self.max_pos
533                        ), indent=4))
534                else:
535                    parser_code.append(libclinic.normalize_snippet("""
536                        %s = PyTuple_GetSlice(%d, -1);
537                        """ % (
538                            p.converter.parser_name,
539                            self.max_pos
540                        ), indent=4))
541                continue
542
543            displayname = p.get_displayname(i+1)
544            argname = argname_fmt % i
545            parsearg: str | None
546            parsearg = p.converter.parse_arg(argname, displayname, limited_capi=self.limited_capi)
547            if parsearg is None:
548                use_parser_code = False
549                parser_code = []
550                break
551            if has_optional or p.is_optional():
552                has_optional = True
553                parser_code.append(libclinic.normalize_snippet("""
554                    if (%s < %d) {{
555                        goto skip_optional;
556                    }}
557                    """, indent=4) % (nargs, i + 1))
558            parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
559
560        if use_parser_code:
561            if has_optional:
562                parser_code.append("skip_optional:")
563        else:
564            for parameter in self.parameters:
565                parameter.converter.use_converter()
566
567            if self.limited_capi:
568                self.fastcall = False
569            if self.fastcall:
570                self.codegen.add_include('pycore_modsupport.h',
571                                         '_PyArg_ParseStack()')
572                parser_code = [libclinic.normalize_snippet("""
573                    if (!_PyArg_ParseStack(args, nargs, "{format_units}:{name}",
574                        {parse_arguments})) {{
575                        goto exit;
576                    }}
577                    """, indent=4)]
578            else:
579                self.flags = "METH_VARARGS"
580                self.parser_prototype = PARSER_PROTOTYPE_VARARGS
581                parser_code = [libclinic.normalize_snippet("""
582                    if (!PyArg_ParseTuple(args, "{format_units}:{name}",
583                        {parse_arguments})) {{
584                        goto exit;
585                    }}
586                    """, indent=4)]
587        self.parser_body(*parser_code)
588
589    def parse_general(self, clang: CLanguage) -> None:
590        parsearg: str | None
591        deprecated_positionals: dict[int, Parameter] = {}
592        deprecated_keywords: dict[int, Parameter] = {}
593        for i, p in enumerate(self.parameters):
594            if p.deprecated_positional:
595                deprecated_positionals[i] = p
596            if p.deprecated_keyword:
597                deprecated_keywords[i] = p
598
599        has_optional_kw = (
600            max(self.pos_only, self.min_pos) + self.min_kw_only
601            < len(self.converters) - int(self.vararg != NO_VARARG)
602        )
603
604        use_parser_code = True
605        if self.limited_capi:
606            parser_code = []
607            use_parser_code = False
608            self.fastcall = False
609        else:
610            if self.vararg == NO_VARARG:
611                self.codegen.add_include('pycore_modsupport.h',
612                                         '_PyArg_UnpackKeywords()')
613                args_declaration = "_PyArg_UnpackKeywords", "%s, %s, %s" % (
614                    self.min_pos,
615                    self.max_pos,
616                    self.min_kw_only
617                )
618                nargs = "nargs"
619            else:
620                self.codegen.add_include('pycore_modsupport.h',
621                                         '_PyArg_UnpackKeywordsWithVararg()')
622                args_declaration = "_PyArg_UnpackKeywordsWithVararg", "%s, %s, %s, %s" % (
623                    self.min_pos,
624                    self.max_pos,
625                    self.min_kw_only,
626                    self.vararg
627                )
628                nargs = f"Py_MIN(nargs, {self.max_pos})" if self.max_pos else "0"
629
630            if self.fastcall:
631                self.flags = "METH_FASTCALL|METH_KEYWORDS"
632                self.parser_prototype = PARSER_PROTOTYPE_FASTCALL_KEYWORDS
633                argname_fmt = 'args[%d]'
634                self.declarations = declare_parser(self.func, codegen=self.codegen)
635                self.declarations += "\nPyObject *argsbuf[%s];" % len(self.converters)
636                if has_optional_kw:
637                    self.declarations += "\nPy_ssize_t noptargs = %s + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - %d;" % (nargs, self.min_pos + self.min_kw_only)
638                parser_code = [libclinic.normalize_snippet("""
639                    args = %s(args, nargs, NULL, kwnames, &_parser, %s, argsbuf);
640                    if (!args) {{
641                        goto exit;
642                    }}
643                    """ % args_declaration, indent=4)]
644            else:
645                # positional-or-keyword arguments
646                self.flags = "METH_VARARGS|METH_KEYWORDS"
647                self.parser_prototype = PARSER_PROTOTYPE_KEYWORD
648                argname_fmt = 'fastargs[%d]'
649                self.declarations = declare_parser(self.func, codegen=self.codegen)
650                self.declarations += "\nPyObject *argsbuf[%s];" % len(self.converters)
651                self.declarations += "\nPyObject * const *fastargs;"
652                self.declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);"
653                if has_optional_kw:
654                    self.declarations += "\nPy_ssize_t noptargs = %s + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - %d;" % (nargs, self.min_pos + self.min_kw_only)
655                parser_code = [libclinic.normalize_snippet("""
656                    fastargs = %s(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, %s, argsbuf);
657                    if (!fastargs) {{
658                        goto exit;
659                    }}
660                    """ % args_declaration, indent=4)]
661
662        if self.requires_defining_class:
663            self.flags = 'METH_METHOD|' + self.flags
664            self.parser_prototype = PARSER_PROTOTYPE_DEF_CLASS
665
666        if use_parser_code:
667            if deprecated_keywords:
668                code = clang.deprecate_keyword_use(self.func, deprecated_keywords,
669                                                   argname_fmt,
670                                                   codegen=self.codegen,
671                                                   fastcall=self.fastcall)
672                parser_code.append(code)
673
674            add_label: str | None = None
675            for i, p in enumerate(self.parameters):
676                if isinstance(p.converter, defining_class_converter):
677                    raise ValueError("defining_class should be the first "
678                                    "parameter (after clang)")
679                displayname = p.get_displayname(i+1)
680                parsearg = p.converter.parse_arg(argname_fmt % i, displayname, limited_capi=self.limited_capi)
681                if parsearg is None:
682                    parser_code = []
683                    use_parser_code = False
684                    break
685                if add_label and (i == self.pos_only or i == self.max_pos):
686                    parser_code.append("%s:" % add_label)
687                    add_label = None
688                if not p.is_optional():
689                    parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
690                elif i < self.pos_only:
691                    add_label = 'skip_optional_posonly'
692                    parser_code.append(libclinic.normalize_snippet("""
693                        if (nargs < %d) {{
694                            goto %s;
695                        }}
696                        """ % (i + 1, add_label), indent=4))
697                    if has_optional_kw:
698                        parser_code.append(libclinic.normalize_snippet("""
699                            noptargs--;
700                            """, indent=4))
701                    parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
702                else:
703                    if i < self.max_pos:
704                        label = 'skip_optional_pos'
705                        first_opt = max(self.min_pos, self.pos_only)
706                    else:
707                        label = 'skip_optional_kwonly'
708                        first_opt = self.max_pos + self.min_kw_only
709                        if self.vararg != NO_VARARG:
710                            first_opt += 1
711                    if i == first_opt:
712                        add_label = label
713                        parser_code.append(libclinic.normalize_snippet("""
714                            if (!noptargs) {{
715                                goto %s;
716                            }}
717                            """ % add_label, indent=4))
718                    if i + 1 == len(self.parameters):
719                        parser_code.append(libclinic.normalize_snippet(parsearg, indent=4))
720                    else:
721                        add_label = label
722                        parser_code.append(libclinic.normalize_snippet("""
723                            if (%s) {{
724                            """ % (argname_fmt % i), indent=4))
725                        parser_code.append(libclinic.normalize_snippet(parsearg, indent=8))
726                        parser_code.append(libclinic.normalize_snippet("""
727                                if (!--noptargs) {{
728                                    goto %s;
729                                }}
730                            }}
731                            """ % add_label, indent=4))
732
733        if use_parser_code:
734            if add_label:
735                parser_code.append("%s:" % add_label)
736        else:
737            for parameter in self.parameters:
738                parameter.converter.use_converter()
739
740            self.declarations = declare_parser(self.func, codegen=self.codegen,
741                                               hasformat=True)
742            if self.limited_capi:
743                # positional-or-keyword arguments
744                assert not self.fastcall
745                self.flags = "METH_VARARGS|METH_KEYWORDS"
746                self.parser_prototype = PARSER_PROTOTYPE_KEYWORD
747                parser_code = [libclinic.normalize_snippet("""
748                    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords,
749                        {parse_arguments}))
750                        goto exit;
751                """, indent=4)]
752                self.declarations = "static char *_keywords[] = {{{keywords_c} NULL}};"
753                if deprecated_positionals or deprecated_keywords:
754                    self.declarations += "\nPy_ssize_t nargs = PyTuple_Size(args);"
755
756            elif self.fastcall:
757                self.codegen.add_include('pycore_modsupport.h',
758                                         '_PyArg_ParseStackAndKeywords()')
759                parser_code = [libclinic.normalize_snippet("""
760                    if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser{parse_arguments_comma}
761                        {parse_arguments})) {{
762                        goto exit;
763                    }}
764                    """, indent=4)]
765            else:
766                self.codegen.add_include('pycore_modsupport.h',
767                                         '_PyArg_ParseTupleAndKeywordsFast()')
768                parser_code = [libclinic.normalize_snippet("""
769                    if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser,
770                        {parse_arguments})) {{
771                        goto exit;
772                    }}
773                    """, indent=4)]
774                if deprecated_positionals or deprecated_keywords:
775                    self.declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);"
776            if deprecated_keywords:
777                code = clang.deprecate_keyword_use(self.func, deprecated_keywords,
778                                                   codegen=self.codegen,
779                                                   fastcall=self.fastcall)
780                parser_code.append(code)
781
782        if deprecated_positionals:
783            code = clang.deprecate_positional_use(self.func, deprecated_positionals)
784            # Insert the deprecation code before parameter parsing.
785            parser_code.insert(0, code)
786
787        assert self.parser_prototype is not None
788        self.parser_body(*parser_code, declarations=self.declarations)
789
790    def copy_includes(self) -> None:
791        # Copy includes from parameters to Clinic after parse_arg()
792        # has been called above.
793        for converter in self.converters:
794            for include in converter.get_includes():
795                self.codegen.add_include(
796                    include.filename,
797                    include.reason,
798                    condition=include.condition)
799
800    def handle_new_or_init(self) -> None:
801        self.methoddef_define = ''
802
803        if self.func.kind is METHOD_NEW:
804            self.parser_prototype = PARSER_PROTOTYPE_KEYWORD
805        else:
806            self.return_value_declaration = "int return_value = -1;"
807            self.parser_prototype = PARSER_PROTOTYPE_KEYWORD___INIT__
808
809        fields: list[str] = list(self.parser_body_fields)
810        parses_positional = 'METH_NOARGS' not in self.flags
811        parses_keywords = 'METH_KEYWORDS' in self.flags
812        if parses_keywords:
813            assert parses_positional
814
815        if self.requires_defining_class:
816            raise ValueError("Slot methods cannot access their defining class.")
817
818        if not parses_keywords:
819            self.declarations = '{base_type_ptr}'
820            self.codegen.add_include('pycore_modsupport.h',
821                                     '_PyArg_NoKeywords()')
822            fields.insert(0, libclinic.normalize_snippet("""
823                if ({self_type_check}!_PyArg_NoKeywords("{name}", kwargs)) {{
824                    goto exit;
825                }}
826                """, indent=4))
827            if not parses_positional:
828                self.codegen.add_include('pycore_modsupport.h',
829                                         '_PyArg_NoPositional()')
830                fields.insert(0, libclinic.normalize_snippet("""
831                    if ({self_type_check}!_PyArg_NoPositional("{name}", args)) {{
832                        goto exit;
833                    }}
834                    """, indent=4))
835
836        self.parser_body(*fields, declarations=self.declarations)
837
838    def process_methoddef(self, clang: CLanguage) -> None:
839        methoddef_cast_end = ""
840        if self.flags in ('METH_NOARGS', 'METH_O', 'METH_VARARGS'):
841            methoddef_cast = "(PyCFunction)"
842        elif self.func.kind is GETTER:
843            methoddef_cast = "" # This should end up unused
844        elif self.limited_capi:
845            methoddef_cast = "(PyCFunction)(void(*)(void))"
846        else:
847            methoddef_cast = "_PyCFunction_CAST("
848            methoddef_cast_end = ")"
849
850        if self.func.methoddef_flags:
851            self.flags += '|' + self.func.methoddef_flags
852
853        self.methoddef_define = self.methoddef_define.replace('{methoddef_flags}', self.flags)
854        self.methoddef_define = self.methoddef_define.replace('{methoddef_cast}', methoddef_cast)
855        self.methoddef_define = self.methoddef_define.replace('{methoddef_cast_end}', methoddef_cast_end)
856
857        self.methoddef_ifndef = ''
858        conditional = clang.cpp.condition()
859        if not conditional:
860            self.cpp_if = self.cpp_endif = ''
861        else:
862            self.cpp_if = "#if " + conditional
863            self.cpp_endif = "#endif /* " + conditional + " */"
864
865            if self.methoddef_define and self.codegen.add_ifndef_symbol(self.func.full_name):
866                self.methoddef_ifndef = METHODDEF_PROTOTYPE_IFNDEF
867
868    def finalize(self, clang: CLanguage) -> None:
869        # add ';' to the end of self.parser_prototype and self.impl_prototype
870        # (they mustn't be None, but they could be an empty string.)
871        assert self.parser_prototype is not None
872        if self.parser_prototype:
873            assert not self.parser_prototype.endswith(';')
874            self.parser_prototype += ';'
875
876        if self.impl_prototype is None:
877            self.impl_prototype = self.impl_definition
878        if self.impl_prototype:
879            self.impl_prototype += ";"
880
881        self.parser_definition = self.parser_definition.replace("{return_value_declaration}", self.return_value_declaration)
882
883        compiler_warning = clang.compiler_deprecated_warning(self.func, self.parameters)
884        if compiler_warning:
885            self.parser_definition = compiler_warning + "\n\n" + self.parser_definition
886
887    def create_template_dict(self) -> dict[str, str]:
888        d = {
889            "docstring_prototype" : self.docstring_prototype,
890            "docstring_definition" : self.docstring_definition,
891            "impl_prototype" : self.impl_prototype,
892            "methoddef_define" : self.methoddef_define,
893            "parser_prototype" : self.parser_prototype,
894            "parser_definition" : self.parser_definition,
895            "impl_definition" : self.impl_definition,
896            "cpp_if" : self.cpp_if,
897            "cpp_endif" : self.cpp_endif,
898            "methoddef_ifndef" : self.methoddef_ifndef,
899        }
900
901        # make sure we didn't forget to assign something,
902        # and wrap each non-empty value in \n's
903        d2 = {}
904        for name, value in d.items():
905            assert value is not None, "got a None value for template " + repr(name)
906            if value:
907                value = '\n' + value + '\n'
908            d2[name] = value
909        return d2
910
911    def parse_args(self, clang: CLanguage) -> dict[str, str]:
912        self.select_prototypes()
913        self.init_limited_capi()
914
915        self.flags = ""
916        self.declarations = ""
917        self.parser_prototype = ""
918        self.parser_definition = ""
919        self.impl_prototype = None
920        self.impl_definition = IMPL_DEFINITION_PROTOTYPE
921
922        # parser_body_fields remembers the fields passed in to the
923        # previous call to parser_body. this is used for an awful hack.
924        self.parser_body_fields: tuple[str, ...] = ()
925
926        if not self.parameters:
927            self.parse_no_args()
928        elif self.use_meth_o():
929            self.parse_one_arg()
930        elif self.has_option_groups():
931            self.parse_option_groups()
932        elif (not self.requires_defining_class
933              and self.pos_only == len(self.parameters) - self.pseudo_args):
934            self.parse_pos_only()
935        else:
936            self.parse_general(clang)
937
938        self.copy_includes()
939        if self.is_new_or_init():
940            self.handle_new_or_init()
941        self.process_methoddef(clang)
942        self.finalize(clang)
943
944        return self.create_template_dict()
945