Commit c40dd27a authored by Thomas Lotze's avatar Thomas Lotze

refactor keyword argument building for function calls (partial fix for ticket 717)

--HG--
extra : transplant_source : %7B%FB%25%5D%D5Z%84%FD%12%CBC%5B%BD%97%D6%93%FCP%E0%A1
parent 4a7b7214
......@@ -3722,11 +3722,10 @@ class GeneralCallNode(CallNode):
# function ExprNode
# positional_args ExprNode Tuple of positional arguments
# keyword_args ExprNode or None Dict of keyword arguments
# starstar_arg ExprNode or None Dict of extra keyword args
type = py_object_type
subexprs = ['function', 'positional_args', 'keyword_args', 'starstar_arg']
subexprs = ['function', 'positional_args', 'keyword_args']
nogil_check = Node.gil_error
......@@ -3734,15 +3733,14 @@ class GeneralCallNode(CallNode):
function = self.function.compile_time_value(denv)
positional_args = self.positional_args.compile_time_value(denv)
keyword_args = self.keyword_args.compile_time_value(denv)
starstar_arg = self.starstar_arg.compile_time_value(denv)
try:
keyword_args.update(starstar_arg)
return function(*positional_args, **keyword_args)
except Exception, e:
self.compile_time_value_error(e)
def explicit_args_kwds(self):
if self.starstar_arg or not isinstance(self.positional_args, TupleNode):
if (self.keyword_args and not isinstance(self.keyword_args, DictNode) or
not isinstance(self.positional_args, TupleNode)):
raise CompileError(self.pos,
'Compile-time keyword arguments must be explicit.')
return self.positional_args.args, self.keyword_args
......@@ -3754,8 +3752,6 @@ class GeneralCallNode(CallNode):
self.positional_args.analyse_types(env)
if self.keyword_args:
self.keyword_args.analyse_types(env)
if self.starstar_arg:
self.starstar_arg.analyse_types(env)
if not self.function.type.is_pyobject:
if self.function.type.is_error:
self.type = error_type
......@@ -3766,9 +3762,6 @@ class GeneralCallNode(CallNode):
self.function = self.function.coerce_to_pyobject(env)
self.positional_args = \
self.positional_args.coerce_to_pyobject(env)
if self.starstar_arg:
self.starstar_arg = \
self.starstar_arg.coerce_to_pyobject(env)
function = self.function
if function.is_name and function.type_entry:
# We are calling an extension type constructor. As long
......@@ -3782,33 +3775,15 @@ class GeneralCallNode(CallNode):
def generate_result_code(self, code):
if self.type.is_error: return
kwargs_call_function = "PyEval_CallObjectWithKeywords"
if self.keyword_args and self.starstar_arg:
code.put_error_if_neg(self.pos,
"PyDict_Update(%s, %s)" % (
self.keyword_args.py_result(),
self.starstar_arg.py_result()))
keyword_code = self.keyword_args.py_result()
elif self.keyword_args:
keyword_code = self.keyword_args.py_result()
elif self.starstar_arg:
keyword_code = self.starstar_arg.py_result()
if self.starstar_arg.type is not Builtin.dict_type:
# CPython supports calling functions with non-dicts, so do we
code.globalstate.use_utility_code(kwargs_call_utility_code)
kwargs_call_function = "__Pyx_PyEval_CallObjectWithKeywords"
else:
keyword_code = None
if not keyword_code:
if not self.keyword_args:
call_code = "PyObject_Call(%s, %s, NULL)" % (
self.function.py_result(),
self.positional_args.py_result())
else:
call_code = "%s(%s, %s, %s)" % (
kwargs_call_function,
call_code = "PyEval_CallObjectWithKeywords(%s, %s, %s)" % (
self.function.py_result(),
self.positional_args.py_result(),
keyword_code)
self.keyword_args.py_result())
code.putln(
"%s = %s; %s" % (
self.result(),
......@@ -5280,6 +5255,102 @@ class DictItemNode(ExprNode):
def __iter__(self):
return iter([self.key, self.value])
class DictMergeNode(ExprNode):
# Dictionary constructor.
#
# base_dict_node DictNode
# key_value_pairs [DictItemNode]
subexprs = ['base_dict_node', 'key_value_pairs']
is_temp = 1
exclude_null_values = False
type = dict_type
def calculate_constant_result(self):
self.constant_result = dict([
item.constant_result for item in self.key_value_pairs])
def compile_time_value(self, denv):
result = self.base_dict_node.compile_time_value(denv)
pairs = [(item.key.compile_time_value(denv), item.value.compile_time_value(denv))
for item in self.key_value_pairs]
try:
result = dict(result)
result.update(pairs)
except Exception, e:
self.compile_time_value_error(e)
else:
return result
def type_dependencies(self, env):
return ()
def infer_type(self, env):
return dict_type
def analyse_types(self, env):
self.base_dict_node.analyse_types(env)
self.base_dict_node = self.base_dict_node.coerce_to_pyobject(env)
for item in self.key_value_pairs:
item.analyse_types(env)
def may_be_none(self):
return False
gil_message = "Constructing Python dict"
def generate_evaluation_code(self, code):
# Custom method used here because key-value
# pairs are evaluated and used one at a time.
code.mark_pos(self.pos)
self.allocate_temp_result(code)
if self.base_dict_node.type is Builtin.dict_type:
code.putln(
"%s = PyDict_Copy(%s); %s" % (
self.result(),
self.base_dict_node.py_result(),
code.error_goto_if_null(self.result(), self.pos)))
else:
# CPython supports calling functions with non-dicts, so do we
code.putln('if (likely(PyDict_Check(%s))) {' %
self.base_dict_node.py_result())
code.putln(
"%s = PyDict_Copy(%s); %s" % (
self.result(),
self.base_dict_node.py_result(),
code.error_goto_if_null(self.result(), self.pos)))
code.putln('} else {')
code.putln(
"%s = PyObject_CallFunctionObjArgs("
"(PyObject*)&PyDict_Type, %s, NULL); %s" % (
self.result(),
self.base_dict_node.py_result(),
code.error_goto_if_null(self.result(), self.pos)))
code.putln('}')
code.put_gotref(self.py_result())
for item in self.key_value_pairs:
item.generate_evaluation_code(code)
code.putln("if (PyDict_GetItem(%s, %s)) {" % (
self.result(),
item.key.py_result()))
code.putln('PyErr_SetString(PyExc_TypeError, '
'"got multiple values for keyword argument"); %s' % (
code.error_goto(self.pos)))
code.putln("}")
code.put_error_if_neg(self.pos,
"PyDict_SetItem(%s, %s, %s)" % (
self.result(),
item.key.py_result(),
item.value.py_result()))
item.generate_disposal_code(code)
item.free_temps(code)
def annotate(self, code):
self.base_dict_node.annotate(code)
for item in self.key_value_pairs:
item.annotate(code)
class ModuleNameMixin(object):
def set_mod_name(self, env):
self.module_name = env.global_scope().qualified_name
......
......@@ -440,7 +440,8 @@ def p_call_parse_args(s, allow_genexp = True):
s.expect(')')
return positional_args, keyword_args, star_arg, starstar_arg
def p_call_build_packed_args(pos, positional_args, keyword_args, star_arg):
def p_call_build_packed_args(
pos, positional_args, keyword_args, star_arg, starstar_arg=None):
arg_tuple = None
keyword_dict = None
if positional_args or not star_arg:
......@@ -457,8 +458,14 @@ def p_call_build_packed_args(pos, positional_args, keyword_args, star_arg):
if keyword_args:
keyword_args = [ExprNodes.DictItemNode(pos=key.pos, key=key, value=value)
for key, value in keyword_args]
keyword_dict = ExprNodes.DictNode(pos,
key_value_pairs = keyword_args)
if starstar_arg:
keyword_dict = ExprNodes.DictMergeNode(
pos,
key_value_pairs = keyword_args,
base_dict_node = starstar_arg)
else:
keyword_dict = ExprNodes.DictNode(
pos, key_value_pairs = keyword_args)
return arg_tuple, keyword_dict
def p_call(s, function):
......@@ -474,7 +481,7 @@ def p_call(s, function):
args = positional_args)
else:
arg_tuple, keyword_dict = p_call_build_packed_args(
pos, positional_args, keyword_args, star_arg)
pos, positional_args, keyword_args, star_arg, starstar_arg)
return ExprNodes.GeneralCallNode(pos,
function = function,
positional_args = arg_tuple,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment