Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: fix issue which dropped the vararg and varkeyword info in transforms #68

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions codetransformer/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,9 @@ class Code:
----------
instrs : iterable of Instruction
A sequence of codetransformer Instruction objects.
argnames : iterable of str, optional
The names of the arguments to the code object.
param_signature : iterable of str, optional
The names of the parameters to the code object. If a parameter name
starts with * or ** it will be interpreted as the vararg or varkeyword.
name : str, optional
The name of this code object.
filename : str, optional
Expand Down Expand Up @@ -298,6 +299,7 @@ class Code:
lnotab
name
names
param_signature
py_lnotab
sparse_instrs
stacksize
Expand All @@ -306,6 +308,7 @@ class Code:
__slots__ = (
'_instrs',
'_argnames',
'_param_signature',
'_argcount',
'_kwonlyargcount',
'_cellvars',
Expand All @@ -320,7 +323,7 @@ class Code:

def __init__(self,
instrs,
argnames=(),
param_signature=(),
*,
cellvars=(),
freevars=(),
Expand All @@ -339,7 +342,7 @@ def __init__(self,
_argnames = []
append_argname = _argnames.append
varg = kwarg = None
for argname in argnames:
for argname in param_signature:
if argname.startswith('**'):
if kwarg is not None:
raise ValueError('cannot specify **kwargs more than once')
Expand All @@ -348,7 +351,9 @@ def __init__(self,
elif argname.startswith('*'):
if varg is not None:
raise ValueError('cannot specify *args more than once')
varg = argname[1:]
if argname != '*':
# lone star, not varg
varg = argname[1:]
argcounter = kwonlyargcount # all following args are kwonly.
continue
argcounter[0] += 1
Expand Down Expand Up @@ -376,6 +381,7 @@ def __init__(self,

self._instrs = instrs
self._argnames = tuple(_argnames)
self._param_signature = tuple(param_signature)
self._argcount = argcount[0]
self._kwonlyargcount = kwonlyargcount[0]
self._cellvars = cellvars
Expand Down Expand Up @@ -497,7 +503,7 @@ def from_pycode(cls, co):

return cls(
filter(bool, sparse_instrs),
argnames=new_paramnames,
param_signature=new_paramnames,
cellvars=co.co_cellvars,
freevars=co.co_freevars,
name=co.co_name,
Expand Down Expand Up @@ -618,15 +624,15 @@ def sparse_instrs(self):
def argcount(self):
"""The number of arguments this code object accepts.

This does not include varargs (\*args).
This does not include varargs (*args).
"""
return self._argcount

@property
def kwonlyargcount(self):
"""The number of keyword only arguments this code object accepts.

This does not include varkwargs (\*\*kwargs).
This does not include varkwargs (**kwargs).
"""
return self._kwonlyargcount

Expand Down Expand Up @@ -665,6 +671,15 @@ def argnames(self):
"""
return self._argnames

@property
def param_signature(self):
"""The string signature for all the parameters to this code object.

Each parameter name is a string. The vararg will be prefixed with '*'
and the varkeyword will be prefixed with '**'.
"""
return self._param_signature

@property
def varnames(self):
"""The names of all of the local variables in this code object.
Expand Down
2 changes: 1 addition & 1 deletion codetransformer/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def transform(self, code, *, name=None, filename=None):

return Code(
post_transform,
code.argnames,
code.param_signature,
cellvars=self.transform_cellvars(code.cellvars),
freevars=self.transform_freevars(code.freevars),
name=name if name is not None else code.name,
Expand Down
32 changes: 30 additions & 2 deletions codetransformer/tests/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
import pytest

from codetransformer.code import Code, Flag, pycode
from codetransformer.instructions import LOAD_CONST, LOAD_FAST, uses_free
from codetransformer.instructions import (
LOAD_CONST,
LOAD_FAST,
RETURN_VALUE,
uses_free,
)


@pytest.fixture(scope='module')
Expand Down Expand Up @@ -178,7 +183,7 @@ def abc_code():
a = LOAD_CONST('a')
b = LOAD_CONST('b')
c = LOAD_CONST('c') # not in instrs
code = Code((a, b), argnames=())
code = Code((a, b), param_signature=())

return (a, b, c), code

Expand Down Expand Up @@ -220,3 +225,26 @@ def code(): # pragma: no cover
buf = StringIO()
code.dis(file=buf)
assert buf.getvalue() == expected


@pytest.mark.parametrize(
'param_signature, has_varargs, has_varkeywords', (
(('a',), False, False),
(('a', 'b'), False, False),
(('a', 'b', '*c'), True, False),
(('a', 'b', '**c'), False, True),
(('a', 'b', '*', 'c'), False, False),
(('a', 'b', '*c', 'd'), True, False),
(('a', 'b', '*c', '**d'), True, True),
(('a', 'b', '*c', 'd', '**e'), True, True),
),
)
def test_param_signature(param_signature, has_varargs, has_varkeywords):
code = Code(
(LOAD_CONST(None), RETURN_VALUE()),
param_signature=param_signature,
)
assert code.param_signature == param_signature

assert code.flags['CO_VARARGS'] == has_varargs
assert code.flags['CO_VARKEYWORDS'] == has_varkeywords
46 changes: 46 additions & 0 deletions codetransformer/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,49 @@ class c(CodeTransformer):
c.context

assert str(e.value) == 'no active transformation context'


def test_nop():
result = object()

@CodeTransformer()
def f():
return result

assert f() is result

@CodeTransformer()
def f(a):
return a

assert f(result) is result

@CodeTransformer()
def f(a=result):
return a

assert f() is result

@CodeTransformer()
def f(*args):
return args[0]

assert f(result) is result

@CodeTransformer()
def f(*args, a):
return a

assert f(a=result) is result

@CodeTransformer()
def f(**kwargs):
return kwargs['a']

assert f(a=result) is result

@CodeTransformer()
def f(a, *b, c, **d):
return a, b, c, d

assert f(1, 2, c=3, d=4) == (1, (2,), 3, {'d': 4})