Repository: cython/cython

Issue:
NumPy integration fails on Python 2.4/64-bit due to Py_ssize_t != Py_intptr_t
See thread `numpy, Py_ssize_t, cython and 64 bits python 2.4` on numpy-discuss.

Migrated from http://trac.cython.org/ticket/114



Discussion:

Resolving patch:
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -5918,6 +5918,9 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
     code_object = None
     binding = False
     def_node = None
+    defaults = None
+    defaults_struct = None
+    defaults_pyobjects = 0
 
     type = py_object_type
     is_temp = 1
@@ -5933,10 +5936,48 @@ def analyse_types(self, env):
                 env.use_utility_code(fused_function_utility_code)
             else:
                 env.use_utility_code(binding_cfunc_utility_code)
+                self.analyse_default_args(env)
 
         #TODO(craig,haoyu) This should be moved to a better place
         self.set_mod_name(env)
 
+    def analyse_default_args(self, env):
+        """
+        Handle non-literal function's default arguments.
+        """
+        nonliteral_objects = []
+        nonliteral_other = []
+        for arg in self.def_node.args:
+            if arg.default and not arg.default.is_literal:
+                arg.is_dynamic = True
+                if arg.type.is_pyobject:
+                    nonliteral_objects.append(arg)
+                else:
+                    nonliteral_other.append(arg)
+        if nonliteral_objects or nonliteral_objects:
+            module_scope = env.global_scope()
+            cname = module_scope.next_id(Naming.defaults_struct_prefix)
+            scope = Symtab.StructOrUnionScope(cname)
+            self.defaults = []
+            for arg in nonliteral_objects:
+                entry = scope.declare_var(arg.name, arg.type, None,
+                                          Naming.arg_prefix + arg.name,
+                                          allow_pyobject=True)
+                self.defaults.append((arg, entry))
+            for arg in nonliteral_other:
+                entry = scope.declare_var(arg.name, arg.type, None,
+                                          Naming.arg_prefix + arg.name,
+                                          allow_pyobject=False)
+                self.defaults.append((arg, entry))
+            entry = module_scope.declare_struct_or_union(
+                None, 'struct', scope, 1, None, cname=cname)
+            self.defaults_struct = scope
+            self.defaults_pyobjects = len(nonliteral_objects)
+            for arg, entry in self.defaults:
+                arg.default_value = '%s->%s' % (
+                    Naming.dynamic_args_cname, entry.cname)
+            self.def_node.defaults_struct = self.defaults_struct.name
+
     def may_be_none(self):
         return False
 
@@ -6018,6 +6059,17 @@ def generate_cyfunction_code(self, code):
                     self.result()))
             code.put_giveref(self.py_result())
 
+        if self.defaults:
+            code.putln(
+                'if (!__Pyx_CyFunction_InitDefaults(%s, sizeof(%s), %d)) %s' % (
+                    self.result(), self.defaults_struct.name,
+                    self.defaults_pyobjects, code.error_goto(self.pos)))
+            defaults = '__Pyx_CyFunction_Defaults(%s, %s)' % (
+                self.defaults_struct.name, self.result())
+            for arg, entry in self.defaults:
+                arg.generate_assignment_code(code, target='%s->%s' % (
+                    defaults, entry.cname))
+
         if self.specialized_cpdefs:
             self.generate_fused_cpdef(code, code_object_result, flags)
 
diff --git a/Cython/Compiler/Naming.py b/Cython/Compiler/Naming.py
--- a/Cython/Compiler/Naming.py
+++ b/Cython/Compiler/Naming.py
@@ -49,6 +49,8 @@
 closure_class_prefix = pyrex_prefix + "scope_struct_"
 lambda_func_prefix = pyrex_prefix + "lambda_"
 module_is_main   = pyrex_prefix + "module_is_main_"
+defaults_struct_prefix = pyrex_prefix + "defaults"
+dynamic_args_cname = pyrex_prefix + "dynamic_args"
 
 args_cname       = pyrex_prefix + "args"
 generator_cname  = pyrex_prefix + "generator"
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py
--- a/Cython/Compiler/Nodes.py
+++ b/Cython/Compiler/Nodes.py
@@ -677,6 +677,7 @@ class CArgDeclNode(Node):
     # is_self_arg    boolean            Is the "self" arg of an extension type method
     # is_type_arg    boolean            Is the "class" arg of an extension type classmethod
     # is_kw_only     boolean            Is a keyword-only argument
+    # is_dynamic     boolean            Non-literal arg stored inside CyFunction
 
     child_attrs = ["base_type", "declarator", "default"]
 
@@ -690,6 +691,7 @@ class CArgDeclNode(Node):
     name_declarator = None
     default_value = None
     annotation = None
+    is_dynamic = 0
 
     def analyse(self, env, nonempty = 0, is_self_arg = False):
         if is_self_arg:
@@ -738,6 +740,21 @@ def annotate(self, code):
         if self.default:
             self.default.annotate(code)
 
+    def generate_assignment_code(self, code, target=None):
+        default = self.default
+        if default is None or default.is_literal:
+            return
+        if target is None:
+            target = self.calculate_default_value_code(code)
+        default.generate_evaluation_code(code)
+        default.make_owned_reference(code)
+        result = default.result_as(self.type)
+        code.putln("%s = %s;" % (target, result))
+        if self.type.is_pyobject:
+            code.put_giveref(default.result())
+        default.generate_post_assignment_code(code)
+        default.free_temps(code)
+
 
 class CBaseTypeNode(Node):
     # Abstract base class for C base type nodes.
@@ -1800,20 +1817,8 @@ def generate_wrapper_functions(self, code):
     def generate_execution_code(self, code):
         # Evaluate and store argument default values
         for arg in self.args:
-            default = arg.default
-            if default:
-                if not default.is_literal:
-                    default.generate_evaluation_code(code)
-                    default.make_owned_reference(code)
-                    result = default.result_as(arg.type)
-                    code.putln(
-                        "%s = %s;" % (
-                            arg.calculate_default_value_code(code),
-                            result))
-                    if arg.type.is_pyobject:
-                        code.put_giveref(default.result())
-                    default.generate_post_assignment_code(code)
-                    default.free_temps(code)
+            if not arg.is_dynamic:
+                arg.generate_assignment_code(code)
         # For Python class methods, create and store function object
         if self.assmt:
             self.assmt.generate_execution_code(code)
@@ -2645,6 +2650,7 @@ class DefNode(FuncDefNode):
     self_in_stararg = 0
     py_cfunc_node = None
     requires_classobj = False
+    defaults_struct = None # Dynamic kwrds structure name
     doc = None
 
     fused_py_func = False
@@ -3512,6 +3518,11 @@ def generate_argument_values_setup_code(self, args, max_positional_args, argtupl
         code.putln("PyObject* values[%d] = {%s};" % (
             max_args, ','.join('0'*max_args)))
 
+        if self.defaults_struct:
+            code.putln('%s *%s = __Pyx_CyFunction_Defaults(%s, %s);' % (
+                self.defaults_struct, Naming.dynamic_args_cname,
+                self.defaults_struct, Naming.self_cname))
+
         # assign borrowed Python default values to the values array,
         # so that they can be overwritten by received arguments below
         for i, arg in enumerate(args):


Test patch:
diff --git a/tests/bugs.txt b/tests/bugs.txt
--- a/tests/bugs.txt
+++ b/tests/bugs.txt
@@ -18,7 +18,6 @@ temp_sideeffects_T654
 class_scope_T671
 slice2_T636
 builtin_subtype_methods_T653
-default_args_T674
 
 # CPython regression tests that don't current work:
 pyregr.test_threadsignals
diff --git a/tests/run/dynamic_args.pyx b/tests/run/dynamic_args.pyx
new file mode 100644
--- /dev/null
+++ b/tests/run/dynamic_args.pyx
@@ -0,0 +1,24 @@
+# mode: run
+# ticket: 674
+
+cdef class Foo:
+    cdef str name
+
+    def __init__(self, name):
+        self.name = name
+
+    def __repr__(self):
+        return '<%s>' % self.name
+
+def test_exttype_args(a, b, c):
+    """
+    >>> f1 = test_exttype_args([1, 2, 3], 123, Foo('Foo'))
+    >>> f2 = test_exttype_args([0], 0, Foo('Bar'))
+    >>> f1()
+    ([1, 2, 3], 123, <Foo>)
+    >>> f2()
+    ([0], 0, <Bar>)
+    """
+    def inner(a=a, int b=b, Foo c=c):
+        return a, b, c
+    return inner