|
2 | 2 |
|
3 | 3 | from __future__ import annotations
|
4 | 4 |
|
| 5 | +import inspect |
5 | 6 | from collections.abc import Sequence
|
6 | 7 | from typing import Final
|
7 | 8 |
|
|
11 | 12 | Assign,
|
12 | 13 | AssignMulti,
|
13 | 14 | BasicBlock,
|
| 15 | + Box, |
14 | 16 | ControlOp,
|
15 | 17 | DeserMaps,
|
| 18 | + Float, |
| 19 | + Integer, |
16 | 20 | LoadAddress,
|
| 21 | + LoadLiteral, |
17 | 22 | Register,
|
| 23 | + TupleSet, |
18 | 24 | Value,
|
19 | 25 | )
|
20 |
| -from mypyc.ir.rtypes import RType, bitmap_rprimitive, deserialize_type |
| 26 | +from mypyc.ir.rtypes import ( |
| 27 | + RType, |
| 28 | + bitmap_rprimitive, |
| 29 | + deserialize_type, |
| 30 | + is_bool_rprimitive, |
| 31 | + is_none_rprimitive, |
| 32 | +) |
21 | 33 | from mypyc.namegen import NameGenerator
|
22 | 34 |
|
23 | 35 |
|
@@ -379,3 +391,85 @@ def all_values_full(args: list[Register], blocks: list[BasicBlock]) -> list[Valu
|
379 | 391 | values.append(op)
|
380 | 392 |
|
381 | 393 | return values
|
| 394 | + |
| 395 | + |
| 396 | +_ARG_KIND_TO_INSPECT: Final = { |
| 397 | + ArgKind.ARG_POS: inspect.Parameter.POSITIONAL_OR_KEYWORD, |
| 398 | + ArgKind.ARG_OPT: inspect.Parameter.POSITIONAL_OR_KEYWORD, |
| 399 | + ArgKind.ARG_STAR: inspect.Parameter.VAR_POSITIONAL, |
| 400 | + ArgKind.ARG_NAMED: inspect.Parameter.KEYWORD_ONLY, |
| 401 | + ArgKind.ARG_STAR2: inspect.Parameter.VAR_KEYWORD, |
| 402 | + ArgKind.ARG_NAMED_OPT: inspect.Parameter.KEYWORD_ONLY, |
| 403 | +} |
| 404 | + |
| 405 | +# Sentinel indicating a value that cannot be represented in a text signature. |
| 406 | +_NOT_REPRESENTABLE = object() |
| 407 | + |
| 408 | + |
| 409 | +def get_text_signature(fn: FuncIR, *, bound: bool = False) -> str | None: |
| 410 | + """Return a text signature in CPython's internal doc format, or None |
| 411 | + if the function's signature cannot be represented. |
| 412 | + """ |
| 413 | + parameters = [] |
| 414 | + mark_self = (fn.class_name is not None) and (fn.decl.kind != FUNC_STATICMETHOD) and not bound |
| 415 | + sig = fn.decl.bound_sig if bound and fn.decl.bound_sig is not None else fn.decl.sig |
| 416 | + # Pre-scan for end of positional-only parameters. |
| 417 | + # This is needed to handle signatures like 'def foo(self, __x)', where mypy |
| 418 | + # currently sees 'self' as being positional-or-keyword and '__x' as positional-only. |
| 419 | + pos_only_idx = -1 |
| 420 | + for idx, arg in enumerate(sig.args): |
| 421 | + if arg.pos_only and arg.kind in (ArgKind.ARG_POS, ArgKind.ARG_OPT): |
| 422 | + pos_only_idx = idx |
| 423 | + for idx, arg in enumerate(sig.args): |
| 424 | + if arg.name.startswith(("__bitmap", "__mypyc")): |
| 425 | + continue |
| 426 | + kind = ( |
| 427 | + inspect.Parameter.POSITIONAL_ONLY |
| 428 | + if idx <= pos_only_idx |
| 429 | + else _ARG_KIND_TO_INSPECT[arg.kind] |
| 430 | + ) |
| 431 | + default: object = inspect.Parameter.empty |
| 432 | + if arg.optional: |
| 433 | + default = _find_default_argument(arg.name, fn.blocks) |
| 434 | + if default is _NOT_REPRESENTABLE: |
| 435 | + # This default argument cannot be represented in a __text_signature__ |
| 436 | + return None |
| 437 | + |
| 438 | + curr_param = inspect.Parameter(arg.name, kind, default=default) |
| 439 | + parameters.append(curr_param) |
| 440 | + if mark_self: |
| 441 | + # Parameter.__init__/Parameter.replace do not accept $ |
| 442 | + curr_param._name = f"${arg.name}" # type: ignore[attr-defined] |
| 443 | + mark_self = False |
| 444 | + return f"{fn.name}{inspect.Signature(parameters)}" |
| 445 | + |
| 446 | + |
| 447 | +def _find_default_argument(name: str, blocks: list[BasicBlock]) -> object: |
| 448 | + # Find assignment inserted by gen_arg_defaults. Assumed to be the first assignment. |
| 449 | + for block in blocks: |
| 450 | + for op in block.ops: |
| 451 | + if isinstance(op, Assign) and op.dest.name == name: |
| 452 | + return _extract_python_literal(op.src) |
| 453 | + return _NOT_REPRESENTABLE |
| 454 | + |
| 455 | + |
| 456 | +def _extract_python_literal(value: Value) -> object: |
| 457 | + if isinstance(value, Integer): |
| 458 | + if is_none_rprimitive(value.type): |
| 459 | + return None |
| 460 | + val = value.numeric_value() |
| 461 | + if is_bool_rprimitive(value.type): |
| 462 | + return bool(val) |
| 463 | + return val |
| 464 | + elif isinstance(value, Float): |
| 465 | + return value.value |
| 466 | + elif isinstance(value, LoadLiteral): |
| 467 | + return value.value |
| 468 | + elif isinstance(value, Box): |
| 469 | + return _extract_python_literal(value.src) |
| 470 | + elif isinstance(value, TupleSet): |
| 471 | + items = tuple(_extract_python_literal(item) for item in value.items) |
| 472 | + if any(itm is _NOT_REPRESENTABLE for itm in items): |
| 473 | + return _NOT_REPRESENTABLE |
| 474 | + return items |
| 475 | + return _NOT_REPRESENTABLE |
0 commit comments