Repository: cython/cython

Issue:
[BUG] Function Wrapper yields unused-variable warning when compiled with pypy
### Describe the bug

In pandas we have a function wrapper that looks like:

```python
def _make_error_func(func_name: str, cls):
    def f(*args, **kwargs):
        raise ValueError(f"NaTType does not support {func_name}")

    f.__name__ = func_name
    if isinstance(cls, str):
        # passed the literal docstring directly
        f.__doc__ = cls
    elif cls is not None:
        f.__doc__ = getattr(cls, func_name).__doc__
    return f
```

The generated c code looks something like:

```c
static struct __pyx_obj_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func *__pyx_freelist_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func[8];
static int __pyx_freecount_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func = 0;

static PyObject *__pyx_tp_new_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func(PyTypeObject *t, CYTHON_UNUSED PyObject *a, CYTHON_UNUSED PyObject *k) {
  PyObject *o;
  #if CYTHON_COMPILING_IN_LIMITED_API
  allocfunc alloc_func = (allocfunc)PyType_GetSlot(t, Py_tp_alloc);
  o = alloc_func(t, 0);
  #else
  #if CYTHON_COMPILING_IN_CPYTHON
  if (likely((int)(__pyx_freecount_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func > 0) & (int)(t->tp_basicsize == sizeof(struct __pyx_obj_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func)))) {
    o = (PyObject*)__pyx_freelist_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func[--__pyx_freecount_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func];
    memset(o, 0, sizeof(struct __pyx_obj_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func));
    (void) PyObject_INIT(o, t);
  } else
  #endif
  {
    o = (*t->tp_alloc)(t, 0);
    if (unlikely(!o)) return 0;
  }
  #endif
  return o;
}

static void __pyx_tp_dealloc_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func(PyObject *o) {
  struct __pyx_obj_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func *p = (struct __pyx_obj_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func *)o;
  #if CYTHON_USE_TP_FINALIZE
  if (unlikely((PY_VERSION_HEX >= 0x03080000 || __Pyx_PyType_HasFeature(Py_TYPE(o), Py_TPFLAGS_HAVE_FINALIZE)) && __Pyx_PyObject_GetSlot(o, tp_finalize, destructor)) && (!PyType_IS_GC(Py_TYPE(o)) || !__Pyx_PyObject_GC_IsFinalized(o))) {
    if (__Pyx_PyObject_GetSlot(o, tp_dealloc, destructor) == __pyx_tp_dealloc_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func) {
      if (PyObject_CallFinalizerFromDealloc(o)) return;
    }
  }
  #endif
  Py_CLEAR(p->__pyx_v_func_name);
  #if CYTHON_COMPILING_IN_CPYTHON
  if (((int)(__pyx_freecount_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func < 8) & (int)(Py_TYPE(o)->tp_basicsize == sizeof(struct __pyx_obj_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func)))) {
    __pyx_freelist_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func[__pyx_freecount_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func++] = ((struct __pyx_obj_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func *)o);
  } else
  #endif
  {
    #if CYTHON_USE_TYPE_SLOTS || CYTHON_COMPILING_IN_PYPY
    (*Py_TYPE(o)->tp_free)(o);
    #else
    {
      freefunc tp_free = (freefunc)PyType_GetSlot(Py_TYPE(o), Py_tp_free);
      if (tp_free) tp_free(o);
    }
    #endif
  }
}
```

Compiling this with warnings yields `error: ‘__pyx_freecount_6pandas_5_libs_6tslibs_7nattype___pyx_scope_struct___make_error_func’ defined but not used [-Werror=unused-variable]`, which makes sense since the variable is only used when `CYTHON_COMPILING_IN_CPYTHON`

### Code to reproduce the behaviour:

See above

### Expected behaviour

No build warnings

### OS

Linux

### Python version

3.11

### Cython version

3.0.9

### Additional context

Example from pandas CI - https://github.com/pandas-dev/pandas/actions/runs/8348473859/job/22850478362?pr=57895#step:7:281


Discussion:

Resolving patch:
diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py
--- a/Cython/Compiler/ModuleNode.py
+++ b/Cython/Compiler/ModuleNode.py
@@ -1546,10 +1546,12 @@ def generate_new_function(self, scope, code, cclass_entry):
                     slot_func)
         code.putln("")
         if freelist_size:
+            code.putln("#if CYTHON_USE_FREELISTS")
             code.putln("static %s[%d];" % (
                 scope.parent_type.declaration_code(freelist_name),
                 freelist_size))
             code.putln("static int %s = 0;" % freecount_name)
+            code.putln("#endif")
             code.putln("")
         code.putln(
             "static PyObject *%s(PyTypeObject *t, %sPyObject *a, %sPyObject *k) {" % (
@@ -1579,7 +1581,7 @@ def generate_new_function(self, scope, code, cclass_entry):
                 else:
                     type_safety_check = ' & (int)(!__Pyx_PyType_HasFeature(t, (Py_TPFLAGS_IS_ABSTRACT | Py_TPFLAGS_HEAPTYPE)))'
                 obj_struct = type.declaration_code("", deref=True)
-                code.putln("#if CYTHON_COMPILING_IN_CPYTHON")
+                code.putln("#if CYTHON_USE_FREELISTS")
                 code.putln(
                     "if (likely((int)(%s > 0) & (int)(t->tp_basicsize == sizeof(%s))%s)) {" % (
                         freecount_name, obj_struct, type_safety_check))
@@ -1820,7 +1822,7 @@ def generate_dealloc_function(self, scope, code):
                         ' & (int)(!__Pyx_PyType_HasFeature(Py_TYPE(o), (Py_TPFLAGS_IS_ABSTRACT | Py_TPFLAGS_HEAPTYPE)))')
 
                 type = scope.parent_type
-                code.putln("#if CYTHON_COMPILING_IN_CPYTHON")
+                code.putln("#if CYTHON_USE_FREELISTS")
                 code.putln(
                     "if (((int)(%s < %d) & (int)(Py_TYPE(o)->tp_basicsize == sizeof(%s))%s)) {" % (
                         freecount_name,
@@ -3418,6 +3420,7 @@ def generate_module_cleanup_func(self, env, code):
                 scope = cclass_type.scope
                 freelist_name = scope.mangle_internal(Naming.freelist_name)
                 freecount_name = scope.mangle_internal(Naming.freecount_name)
+                code.putln('#if CYTHON_USE_FREELISTS')
                 code.putln("while (%s > 0) {" % freecount_name)
                 code.putln("PyObject* o = (PyObject*)%s[--%s];" % (
                     freelist_name, freecount_name))
@@ -3429,6 +3432,7 @@ def generate_module_cleanup_func(self, env, code):
                 code.putln("if (tp_free) tp_free(o);")
                 code.putln("#endif")
                 code.putln("}")
+                code.putln('#endif')  # CYTHON_USE_FREELISTS
 #        for entry in env.pynum_entries:
 #            code.put_decref_clear(entry.cname,
 #                                  PyrexTypes.py_object_type,


Test patch: