fix(extraction): drop the phantom C++ function from a macro-annotated class misparse (#946) (#948)

A C++ class/struct annotated with an export/visibility macro —
`class MYLIB_EXPORT Foo : public Bar { … }` — makes tree-sitter read
`class MYLIB_EXPORT` as an elaborated type specifier and the whole declaration
as a `function_definition` named after the class, spanning the entire body. That
phantom `function` polluted callers/impact/blast-radius and skewed kind stats.

Detect the misparse structurally in cppExtractor.isMisparsedFunction — a
function_definition whose `type` field is a *bodyless* class/struct specifier
(the elaborated-type macro) and whose declarator is not a function_declarator —
and drop the bogus node, matching how macro-prefixed C prototypes are already
handled. The body is mangled by the same misparse and is unrecoverable. Precise
enough to leave genuine code alone: `struct P { int x; } makeP() {}` (real
inline-defined return type, has a field list) and `class Foo f() {}` (elaborated
return type on a real function, has a function_declarator) are untouched. The
leading macro alone triggers the misparse; a base clause is not required.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-06-22 08:45:50 -05:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 2bdc169ce4
commit f63e5db2cc
3 changed files with 97 additions and 2 deletions
+39 -2
View File
@@ -148,6 +148,40 @@ export const cExtractor: LanguageExtractor = {
},
};
/**
* Detect tree-sitter's misparse of a macro-annotated class/struct, e.g.
* `class MACRO Name { … }` or `class MACRO Name : public Base { … }` (#946).
* Not knowing `MACRO` is a macro, tree-sitter reads `class MACRO` as an
* *elaborated type specifier* (a bodyless `class_specifier`/`struct_specifier`
* whose "type name" is the macro) and the rest as a function: `Name` becomes the
* declarator and the `{ … }` a function body — so the whole declaration surfaces
* as a `function_definition` named after the class, with a line range spanning
* the entire class body. (A base clause, when present, additionally lands in an
* `ERROR` node, but it isn't required — the leading macro alone triggers this.)
*
* Two structural signals pin it down with no risk to genuine code:
* - the `type` field is a *bodyless* class/struct specifier — an elaborated
* type, not a real inline-defined return type like
* `struct P { int x; } makeP() { … }` (which carries a field list); and
* - the declarator is not a `function_declarator` — a real function definition
* always has one, which also leaves the legal-but-rare `class Foo f() { … }`
* (an elaborated return type on a genuine function) alone.
*
* The class body is mangled by the same misparse and is unrecoverable, so —
* matching how macro-prefixed C prototypes are handled — we drop the spurious
* node rather than mint a misleading whole-body `function` that pollutes
* callers/impact and skews kind statistics.
*/
function isMacroMisparsedTypeDecl(node: SyntaxNode): boolean {
const typeNode = getChildByField(node, 'type');
if (!typeNode) return false;
if (typeNode.type !== 'class_specifier' && typeNode.type !== 'struct_specifier') return false;
if (typeNode.namedChildren.some((c: SyntaxNode) => c.type === 'field_declaration_list')) return false;
const declarator = getChildByField(node, 'declarator');
if (declarator && declarator.type === 'function_declarator') return false;
return true;
}
export const cppExtractor: LanguageExtractor = {
functionTypes: ['function_definition'],
classTypes: ['class_specifier'],
@@ -192,14 +226,17 @@ export const cppExtractor: LanguageExtractor = {
}
return undefined;
},
isMisparsedFunction: (name) => {
isMisparsedFunction: (name, node) => {
// C++ macros like NLOHMANN_JSON_NAMESPACE_BEGIN cause tree-sitter to misparse
// namespace blocks as function_definitions (e.g. name = "namespace detail").
// Also filter C++ keywords that tree-sitter occasionally misinterprets as
// function/method names (e.g. switch statements inside macro-confused scopes).
if (name.startsWith('namespace')) return true;
const cppKeywords = ['switch', 'if', 'for', 'while', 'do', 'case', 'return'];
return cppKeywords.includes(name);
if (cppKeywords.includes(name)) return true;
// `class MACRO Name : public Base { … }` misparses to a function_definition
// named after the class — drop that phantom (#946).
return isMacroMisparsedTypeDecl(node);
},
extractImport: (node, source) => {
const importText = source.substring(node.startIndex, node.endIndex).trim();