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:
co-authored by
Claude Opus 4.8
parent
2bdc169ce4
commit
f63e5db2cc
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user