Repository: cython/cython

Issue:
[BUG] Complex function argument lists causing MSVC to fail
### Describe the bug

I suspect this is a variant of the bug described in #5290 as I am seeing the same compiler error:

```
      src/flitter/render/physics.c(22571): error C2039: '__pyx_mstate_global': is not a member of '__pyx_mstate'
      src/flitter/render/physics.c(3910): note: see declaration of '__pyx_mstate'
```

The line that it is exploding on is

```C
  if ((CYTHON_USE_TYPE_SLOTS && CYTHON_USE_PYTYPE_LOOKUP) && likely(!__pyx_ptype___pyx_scope_struct____Pyx_CFunc_3ffa58__double__lParenPhysicsSystem__comma_list__comma_list__comma_list__comma_list__comma_list__comma_list__comma_int__comma_bint__comma_bint__comma_double__comma_double__comma_double__comma_double__comma_double__rParen__etc_to_py_4self_9particles_11non_anchors_15particle_forces_13matrix_forces_15specific_forces_8barriers_10dimensions_8realtime_11extra_frame_14speed_of_light_4time_10start_time_10resolution_5clock->tp_dictoffset && __pyx_ptype___pyx_scope_struct____Pyx_CFunc_3ffa58__double__lParenPhysicsSystem__comma_list__comma_list__comma_list__comma_list__comma_list__comma_list__comma_int__comma_bint__comma_bint__comma_double__comma_double__comma_double__comma_double__comma_double__rParen__etc_to_py_4self_9particles_11non_anchors_15particle_forces_13matrix_forces_15specific_forces_8barriers_10dimensions_8realtime_11extra_frame_14speed_of_light_4time_10start_time_10resolution_5clock->tp_getattro == PyObject_GenericGetAttr)) {
```

and this looks to be generated indirectly from this function signature:

```cython
cdef class PhysicsSystem:

    ...

    cdef double calculate(self, list particles, list non_anchors, list particle_forces, list matrix_forces, list specific_forces, list barriers,
                          int dimensions, bint realtime, bint extra_frame, double speed_of_light,
                          double time, double start_time, double resolution, double clock):
        ...
```

Now I recognise that this is a lot of parameters, but it doesn't seem excessive.

### Code to reproduce the behaviour:

_No response_

### Expected behaviour

_No response_

### OS

Windows 11 64-bit

### Python version

3.12.2

### Cython version

3.0.8

### Additional context

I will attempt to reduce this to a smaller, full, failing example, but I'm a bit pressed for time this morning.


Discussion:
OK, tried a test of combining all of the arguments together into a single `args` tuple a la:

```cython
    cdef double calculate(self, tuple args):
        cdef list particles, non_anchors, particle_forces, matrix_forces, specific_forces, barriers
        cdef int dimensions
        cdef bint realtime, extra_frame
        cdef double speed_of_light, time, start_time, resolution, clock
        particles, non_anchors, particle_forces, matrix_forces, specific_forces, barriers, dimensions, realtime, extra_frame, speed_of_light, time, start_time, resolution, clock = args
        ...
```

(and similarly combining at the call site) and this compiles and seems to be OK.
Oh! The failing MSVC is:

```
error: command 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.38.33130\\bin\\HostX86\\x64\\cl.exe' failed with exit code 2
```

In case the version number is important.
Ah. The name mangling in 3.0.9 has become even more verbose it seems. I had removed a bunch of parameters in order to get this to compile and now it's failing again…

I was down to just:

```cython
cdef class PhysicsSystem:
    ...
    cdef double calculate(self, tuple objects, bint realtime, bint extra_frame, double speed_of_light, double time, double clock):
        ...
```

which doesn't feel like an excessive method signature, but is uncompilable on Windows.

Would it make sense for the name mangler to truncate long names and add a hash of the full string on the end instead?
I see that there already is a `cap_length()` function in `PyrexTypes.py`, but I assume that is not being used in this particular instance.

I'd like to dig further, but I'm not sure I can take on learning another codebase on top of everything I'm already doing at the moment…
@da-woods If you could have a look that would be great. I hit the same error message in https://github.com/scikit-learn/scikit-learn/pull/28638.

Resolving patch:
diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py
--- a/Cython/Compiler/PyrexTypes.py
+++ b/Cython/Compiler/PyrexTypes.py
@@ -3490,8 +3490,12 @@ def create_to_py_utility_code(self, env):
         def arg_name_part(arg):
             return "%s%s" % (len(arg.name), punycodify_name(arg.name)) if arg.name else "0"
         arg_names = [ arg_name_part(arg) for arg in self.args ]
-        arg_names = "_".join(arg_names)
+        arg_names = cap_length("_".join(arg_names))
         safe_typename = type_identifier(self, pyrex=True)
+        # Note that the length here is slightly bigger than twice the default cap in
+        # "cap_length" (since the length is capped in both arg_names and the type_identifier)
+        # but since this is significantly shorter than compilers should be able to handle,
+        # that is acceptable.
         to_py_function = "__Pyx_CFunc_%s_to_py_%s" % (safe_typename, arg_names)
 
         for arg in self.args:
@@ -5486,8 +5490,8 @@ def type_identifier_from_declaration(decl, scope = None):
         _type_identifier_cache[key] = safe
     return safe
 
-def cap_length(s, max_prefix=63, max_len=1024):
-    if len(s) <= max_prefix:
+def cap_length(s, max_len=63):
+    if len(s) <= max_len:
         return s
     hash_prefix = hashlib.sha256(s.encode('ascii')).hexdigest()[:6]
     return '%s__%s__etc' % (hash_prefix, s[:max_len-17])


Test patch:
diff --git a/Cython/Compiler/Tests/TestTypes.py b/Cython/Compiler/Tests/TestTypes.py
--- a/Cython/Compiler/Tests/TestTypes.py
+++ b/Cython/Compiler/Tests/TestTypes.py
@@ -59,9 +59,9 @@ def test_type_identifier_for_declaration(self):
             ("const &&&std::vector", "const__fwref__refstd__in_vector"),
             ("const &&std::vector", "const__fwrefstd__in_vector"),
             ("void (*func)(int x, float y)",
-             "975d51__void__lParen__ptrfunc__rParen__lParenint__space_x__comma_float__space_y__rParen__etc"),
+             "975d51__void__lParen__ptrfunc__rParen__lParenint__spac__etc"),
             ("float ** (*func)(int x, int[:] y)",
-             "31883a__float__ptr__ptr__lParen__ptrfunc__rParen__lParenint__space_x__comma_int__lArr__D__rArry__rParen__etc"),
+             "31883a__float__ptr__ptr__lParen__ptrfunc__rParen__lPar__etc"),
         ]
         self._test_escape(function_name, test_data)
 
@@ -71,3 +71,5 @@ def _test_escape(self, func_name, test_data=TEST_DATA):
             escaped_value = escape(declaration)
             self.assertEqual(escaped_value, expected, "%s('%s') == '%s' != '%s'" % (
                 func_name, declaration, escaped_value, expected))
+            # test that the length has been successfully capped
+            self.assertLessEqual(len(escaped_value), 64)