Skip to content

Commit 70f7609

Browse files
martindemellorchen152
authored andcommitted
rewrite: Basic CALL_FUNCTION_EX support.
Supports unpacking a concrete iterable in a function call. PiperOrigin-RevId: 623580582
1 parent fe65fc5 commit 70f7609

4 files changed

Lines changed: 62 additions & 1 deletion

File tree

pytype/rewrite/abstract/abstract.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
Tuple = _containers.Tuple
3838

3939
ConstKeyDict = _internal.ConstKeyDict
40+
FunctionArgTuple = _internal.FunctionArgTuple
4041
Splat = _internal.Splat
4142

4243
get_atomic_constant = _utils.get_atomic_constant

pytype/rewrite/abstract/internal.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Abstract types used internally by pytype."""
22

3-
from typing import Any, Dict, Sequence
3+
from typing import Dict, Sequence, Tuple
44

55
import immutabledict
66

@@ -30,6 +30,22 @@ def _attrs(self):
3030
return (immutabledict.immutabledict(self.constant),)
3131

3232

33+
class FunctionArgTuple(base.BaseValue):
34+
"""Representation of a function arg tuple."""
35+
36+
def __init__(self, ctx: base.ContextType, constant: Tuple[_Variable, ...]):
37+
super().__init__(ctx)
38+
assert isinstance(constant, tuple), constant
39+
self.constant = constant
40+
41+
def __repr__(self):
42+
return f"FunctionArgTuple({self.constant!r})"
43+
44+
@property
45+
def _attrs(self):
46+
return (self.constant,)
47+
48+
3349
class Splat(base.BaseValue):
3450
"""Representation of unpacked iterables.
3551

pytype/rewrite/frame.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,40 @@ def byte_CALL_FUNCTION(self, opcode):
571571
callargs = abstract.Args(posargs=tuple(args), frame=self)
572572
self._call_function(func, callargs)
573573

574+
def _unpack_starargs(self, starargs):
575+
# TODO(b/331853896): This follows vm_utils.ensure_unpacked_starargs, but
576+
# does not yet handle indefinite iterables.
577+
posargs = starargs.get_atomic_value()
578+
if isinstance(posargs, abstract.FunctionArgTuple):
579+
# This has already been converted
580+
pass
581+
elif isinstance(posargs, abstract.Tuple):
582+
posargs = abstract.FunctionArgTuple(self._ctx, posargs.constant)
583+
elif isinstance(posargs, tuple):
584+
posargs = abstract.FunctionArgTuple(self._ctx, posargs)
585+
else:
586+
assert False, f'unexpected posargs type: {posargs}: {type(posargs)}'
587+
return posargs
588+
589+
def _unpack_starstarargs(self, starstarargs):
590+
kwargs = abstract.get_atomic_constant(starstarargs, dict)
591+
return {abstract.get_atomic_constant(k, str): v
592+
for k, v in kwargs.items()}
593+
594+
def byte_CALL_FUNCTION_EX(self, opcode):
595+
if opcode.arg & _Flags.CALL_FUNCTION_EX_HAS_KWARGS:
596+
starstarargs = self._stack.pop()
597+
kwargs = self._unpack_starstarargs(starstarargs)
598+
else:
599+
kwargs = _EMPTY_MAP
600+
starargs = self._stack.pop()
601+
posargs = self._unpack_starargs(starargs).constant
602+
func = self._stack.pop()
603+
if self._code.python_version >= (3, 11):
604+
self._stack.pop_and_discard()
605+
callargs = abstract.Args(posargs=posargs, kwargs=kwargs, frame=self)
606+
self._call_function(func, callargs)
607+
574608
def byte_CALL_METHOD(self, opcode):
575609
args = self._stack.popn(opcode.arg)
576610
func = self._stack.pop()

pytype/rewrite/frame_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,16 @@ def f(x, *, y):
635635
self.assertConstantVar(callargs.posargs[0], 1)
636636
self.assertConstantVar(callargs.kwargs['y'], 2)
637637

638+
@test_utils.skipBeforePy((3, 11), 'Relies on 3.11+ bytecode')
639+
def test_call_function_ex_no_crash(self):
640+
frame = self._make_frame("""
641+
def f(x, y, z):
642+
pass
643+
a = (1, 2)
644+
f(*a, z=3)
645+
""")
646+
frame.run()
647+
638648

639649
if __name__ == '__main__':
640650
unittest.main()

0 commit comments

Comments
 (0)