implement #470: non-dicts as **kwargs
authorStefan Behnel <scoder@users.berlios.de>
Thu, 14 Jan 2010 07:36:13 +0000 (08:36 +0100)
committerStefan Behnel <scoder@users.berlios.de>
Thu, 14 Jan 2010 07:36:13 +0000 (08:36 +0100)
Cython/Compiler/ExprNodes.py
tests/run/non_dict_kwargs_T470.pyx [new file with mode: 0644]

index d2789833eef8cd1f593f5f288f6b6dfcc4845ed1..5c0799877ad3b51ce7a662dbbe49d5798d49c7ad 100644 (file)
@@ -2775,6 +2775,7 @@ class GeneralCallNode(CallNode):
         
     def generate_result_code(self, code):
         if self.type.is_error: return
+        dict_temp = dict_ref = None
         if self.keyword_args and self.starstar_arg:
             code.put_error_if_neg(self.pos, 
                 "PyDict_Update(%s, %s)" % (
@@ -2785,6 +2786,23 @@ class GeneralCallNode(CallNode):
             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
+                dict_temp = code.funcstate.allocate_temp(py_object_type, manage_ref=False)
+                dict_ref = code.funcstate.allocate_temp(py_object_type, manage_ref=True)
+                code.putln("if (unlikely(!PyDict_Check(%s))) {" % keyword_code)
+                code.putln(
+                    "%s = PyObject_CallFunctionObjArgs((PyObject*)&PyDict_Type, %s, NULL); %s" % (
+                        dict_ref,
+                        keyword_code,
+                        code.error_goto_if_null(dict_ref, self.pos)))
+                code.put_gotref(dict_ref)
+                code.putln("%s = %s;" % (dict_temp, dict_ref))
+                code.putln("} else {")
+                code.putln("%s = 0;" % dict_ref)
+                code.putln("%s = %s;" % (dict_temp, keyword_code))
+                code.putln("}")
+                keyword_code = dict_temp
         else:
             keyword_code = None
         if not keyword_code:
@@ -2802,6 +2820,10 @@ class GeneralCallNode(CallNode):
                 call_code,
                 code.error_goto_if_null(self.result(), self.pos)))
         code.put_gotref(self.py_result())
+        if dict_ref is not None:
+            code.funcstate.release_temp(dict_temp)
+            code.put_xdecref_clear(dict_ref, py_object_type)
+            code.funcstate.release_temp(dict_ref)
 
 
 class AsTupleNode(ExprNode):
diff --git a/tests/run/non_dict_kwargs_T470.pyx b/tests/run/non_dict_kwargs_T470.pyx
new file mode 100644 (file)
index 0000000..1a4f17b
--- /dev/null
@@ -0,0 +1,52 @@
+__doc__ = u"""
+>>> func(**{'a' : 7})
+True
+>>> func(**SubDict())
+True
+
+>>> call_non_dict_test()
+True
+>>> call_non_dict_test_kw()
+True
+
+>>> call_sub_dict_test()
+True
+>>> call_sub_dict_test_kw()
+True
+"""
+
+import sys
+
+if sys.version_info >= (2,6):
+    __doc__ += u"""
+>>> func(**NonDict())
+True
+"""
+
+def func(**kwargs):
+    return type(kwargs) is dict and kwargs['a'] == 7
+
+
+class NonDict(object):
+   def __getitem__(self, k):
+       assert k == 'a'
+       return 7
+   def keys(self):
+       return ['a']
+
+def call_non_dict_test():
+    return func(**NonDict())
+
+def call_non_dict_test_kw():
+    return func(a=5, **NonDict())
+
+
+class SubDict(dict):
+    def __init__(self):
+        self['a'] = 7
+
+def call_sub_dict_test():
+    return func(**SubDict())
+
+def call_sub_dict_test_kw():
+    return func(a=5, **SubDict())