@@ -222,6 +222,123 @@ def detect_universal_ctags() -> bool:
222
222
except Exception :
223
223
return False
224
224
225
+ def normalize_function_signature (signature : str ) -> str :
226
+ """
227
+ Normalize a function signature by removing parameter names, keeping only types.
228
+
229
+ This handles cases where header declarations and implementations have different parameter names.
230
+ Uses a simple heuristic: the last word in each parameter is typically the parameter name.
231
+
232
+ For example:
233
+ - "ltoa(long val, char *s, int radix)" -> "ltoa(long,char *,int)"
234
+ - "ltoa(long value, char *result, int base)" -> "ltoa(long,char *,int)"
235
+
236
+ Args:
237
+ signature: The function signature string, e.g., "(long val, char *s, int radix)"
238
+
239
+ Returns:
240
+ Normalized signature with parameter names removed, e.g., "(long,char *,int)"
241
+ """
242
+ if not signature :
243
+ return signature
244
+
245
+ # Normalize signatures: treat (void) and () as equivalent (both mean no parameters)
246
+ if signature == "(void)" :
247
+ return "()"
248
+
249
+ if not (signature .startswith ("(" ) and signature .endswith (")" )):
250
+ return signature
251
+
252
+ # Handle const qualifier at the end (e.g., "(int i) const")
253
+ const_qualifier = ""
254
+ if signature .endswith (" const" ):
255
+ signature = signature [:- 6 ] # Remove " const"
256
+ const_qualifier = " const"
257
+
258
+ # Extract parameter list without parentheses
259
+ param_list = signature [1 :- 1 ]
260
+ if not param_list .strip ():
261
+ return "()" + const_qualifier
262
+
263
+ # Split by comma and process each parameter
264
+ params = []
265
+ for param in param_list .split ("," ):
266
+ param = param .strip ()
267
+ if not param :
268
+ continue
269
+
270
+ # Handle default parameters (e.g., "int x = 5")
271
+ if "=" in param :
272
+ param = param .split ("=" )[0 ].strip ()
273
+
274
+ # Try simple approach first: remove the last word
275
+ # This works for most cases: "int x" -> "int", "MyStruct s" -> "MyStruct"
276
+ import re
277
+
278
+ # Handle arrays first: "int arr[10]" -> "int [10]", "char *argv[]" -> "char *[]"
279
+ array_match = re .match (r'^(.+?)\s+(\w+)((?:\[\d*\])+)$' , param )
280
+ if array_match :
281
+ type_part = array_match .group (1 ).strip ()
282
+ array_brackets = array_match .group (3 )
283
+ params .append (type_part + array_brackets )
284
+ else :
285
+ # Handle function pointers: "int (*func)(int, char)" -> "int (*)(int, char)"
286
+ func_ptr_match = re .match (r'^(.+?)\s*\(\s*\*\s*(\w+)\s*\)\s*\((.+?)\)\s*$' , param )
287
+ if func_ptr_match :
288
+ return_type = func_ptr_match .group (1 ).strip ()
289
+ inner_params = func_ptr_match .group (3 ).strip ()
290
+ # Recursively normalize the inner parameters
291
+ if inner_params :
292
+ inner_normalized = normalize_function_signature (f"({ inner_params } )" )
293
+ inner_normalized = inner_normalized [1 :- 1 ] # Remove outer parentheses
294
+ else :
295
+ inner_normalized = ""
296
+ params .append (f"{ return_type } (*)({ inner_normalized } )" )
297
+ else :
298
+ # Try simple approach: remove the last word
299
+ simple_match = re .match (r'^(.+)\s+(\w+)$' , param )
300
+ if simple_match :
301
+ # Simple case worked - just remove the last word
302
+ type_part = simple_match .group (1 ).strip ()
303
+ params .append (type_part )
304
+ else :
305
+ # Fallback to complex regex for edge cases with pointers
306
+ # First, try to match cases with pointers/references (including multiple *)
307
+ # Pattern: (everything before) (one or more * or &) (space) (parameter name)
308
+ m = re .match (r'^(.+?)(\s*[*&]+\s+)(\w+)$' , param )
309
+ if m :
310
+ # Keep everything before the pointers, plus the pointers (without the space before param name)
311
+ type_part = m .group (1 ) + m .group (2 ).rstrip ()
312
+ params .append (type_part .strip ())
313
+ else :
314
+ # Try to match cases without space between type and pointer: "char*ptr", "char**ptr"
315
+ m = re .match (r'^(.+?)([*&]+)(\w+)$' , param )
316
+ if m :
317
+ # Keep everything before the pointers, plus the pointers
318
+ type_part = m .group (1 ) + m .group (2 )
319
+ params .append (type_part .strip ())
320
+ else :
321
+ # Single word - assume it's a type
322
+ params .append (param .strip ())
323
+
324
+ # Normalize spacing around pointers to ensure consistent output
325
+ # This ensures "char *" and "char*" both become "char *"
326
+ if params :
327
+ last_param = params [- 1 ]
328
+ # Normalize spacing around * and & symbols
329
+ # Replace multiple spaces with single space, ensure space before * and &
330
+ normalized = re .sub (r'\s+' , ' ' , last_param ) # Collapse multiple spaces
331
+ normalized = re .sub (r'\s*([*&]+)' , r' \1' , normalized ) # Ensure space before * and &
332
+ normalized = re .sub (r'([*&]+)\s*' , r'\1 ' , normalized ) # Ensure space after * and &
333
+ normalized = re .sub (r'\s+' , ' ' , normalized ).strip () # Clean up extra spaces
334
+ params [- 1 ] = normalized
335
+
336
+ result = "(" + "," .join (params ) + ")"
337
+ if const_qualifier :
338
+ result += const_qualifier
339
+
340
+ return result
341
+
225
342
def build_qname_from_tag (tag : dict ) -> str :
226
343
"""
227
344
Compose a qualified name for a function/method using scope + name + signature.
@@ -231,9 +348,8 @@ def build_qname_from_tag(tag: dict) -> str:
231
348
name = tag .get ("name" ) or ""
232
349
signature = tag .get ("signature" ) or ""
233
350
234
- # Normalize signatures: treat (void) and () as equivalent (both mean no parameters)
235
- if signature == "(void)" :
236
- signature = "()"
351
+ # Normalize the signature to remove parameter names
352
+ signature = normalize_function_signature (signature )
237
353
238
354
qparts = []
239
355
if scope :
0 commit comments