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