fix(c): blank leading attribute macros so functions index under real names (#1311)

SEC_ATTR UINT32 LostName(VOID) — an unknown attribute macro before a
typedef'd return type — misparses in tree-sitter's C grammar: the macro
becomes the type, the return type the declarator, and the PARAMETER
LIST is stored as the function name ("(VOID)"). The C++ grammar
recovers this shape via recoverMangledCppName, but in C the real name
never reaches the mangled string, so only a pre-parse blank can help.

Attribute macros are project-specific, so the blank keys on structure:
line-leading ALL-CAPS token followed by TWO identifiers then `(` — the
`MACRO Ret name(` definition shape. Plain typedef'd returns, ALL-CAPS
calls, #define lines, multi-word builtin returns, and mid-line uses are
all rejected by construction. Offset-preserving like the C++ blanks.

curl re-index: 5,531 C functions before and after, zero name changes;
7 nodes in memdebug.c improve start-line accuracy by 1 (the macro line
no longer counts as part of the definition).

Fixes #1211

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-16 15:17:57 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent e437918026
commit b6a05d155b
3 changed files with 95 additions and 5 deletions
+40 -4
View File
@@ -709,11 +709,47 @@ function preParseCppSource(source: string, filePath?: string): string {
return blanked;
}
/** C source pre-processing: C-detected headers in CUDA projects (llm.c keeps
* `__device__` helpers and kernel prototypes in plain `.h`) get the same
* content-gated CUDA blank as C++. */
/**
* Blank an unknown attribute macro sitting in front of a C function
* definition's return type: `SEC_ATTR UINT32 LostName(VOID) { … }` (macro
* wrapping `__attribute__((…))`, common in embedded/kernel C). tree-sitter's
* C grammar reads the macro as the declaration's type, the real return type
* as the declarator, and stores the PARAMETER LIST as the function name —
* `LostName` indexes as `"(VOID)"` and is unfindable (#1211). The C++ grammar
* recovers this shape differently (glued name, salvaged post-hoc by
* `recoverMangledCppName`), but in C the real name never reaches the mangled
* string, so only a pre-parse blank can recover it.
*
* Attribute macros are project-specific (`SEC_ATTR`, `INIT_TEXT`, …), so this
* keys on structure, not a curated list, matched tightly:
* - line-leading (`^[ \t]*`) — declaration position, never an expression use;
* - ALL-CAPS token of ≥3 chars (`[A-Z][A-Z0-9_]{2,}`) — ordinary C types in
* definitions are rarely spelled this way, and when they are (`UINT32 f()`)
* they're followed by ONE identifier + `(`, which the lookahead rejects;
* - followed by TWO identifier tokens (return type, then name — `*` allowed
* for pointer returns) and then `(` — i.e. exactly the
* `MACRO Ret name(` definition shape. `MACRO name(` calls, `#define`
* lines (start with `#`), and multi-word builtin returns
* (`MACRO unsigned int f(` — where the C grammar already keeps the name)
* are all left untouched.
* Equal-length spaces preserve every byte offset, like the C++ blanks above.
*/
const C_LEADING_ATTR_MACRO_RE =
/^([ \t]*)([A-Z][A-Z0-9_]{2,})(?=\s+[A-Za-z_]\w*[\s*]+[A-Za-z_]\w*\s*\()/gm;
export function blankCLeadingAttrMacros(source: string): string {
return source.replace(
C_LEADING_ATTR_MACRO_RE,
(_m, ws: string, macro: string) => ws + ' '.repeat(macro.length)
);
}
/** C source pre-processing: recover functions hidden behind a leading
* attribute macro (#1211), then — for C-detected headers in CUDA projects
* (llm.c keeps `__device__` helpers and kernel prototypes in plain `.h`) —
* the same content-gated CUDA blank as C++. Offset-preserving. */
function preParseCSource(source: string): string {
return looksLikeCudaSource(source) ? blankCudaConstructs(source) : source;
const blanked = blankCLeadingAttrMacros(source);
return looksLikeCudaSource(blanked) ? blankCudaConstructs(blanked) : blanked;
}
export const cppExtractor: LanguageExtractor = {