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