A C++ class annotated with an export/visibility macro between `class`/`struct` and the type name — `class MYMODULE_API UMyComponent : public UActorComponent` (the standard Unreal-Engine `*_API` pattern), or the equivalent `*_EXPORT`/ `*_ABI` macros in Qt, Boost, LLVM, etc. — makes tree-sitter read `class MACRO` as an elaborated type and the whole declaration as a function. #946 dropped the resulting phantom function, but that also discarded the recoverable class name, members, and base-class edge, so the class never entered the graph and "find subclasses" / type-hierarchy / impact-through-inheritance returned nothing for effectively every gameplay class in a UE project. Add `blankCppExportMacros` as `cppExtractor.preParse`: it blanks the macro with equal-length spaces before parsing (offset-preserving, like C#'s `blankCsharpPreprocessorDirectives`/#237), so the declaration parses as a normal class_specifier and existing extraction emits the node, members, and `extends` edge. Generalized past UE `*_API` to any all-caps export macro, with two false-positive guards: the trailing `[:{]` definition-guard (leaves elaborated var decls like `struct FOO var;` alone) and requiring the macro to be followed by the real name (leaves an all-caps class NAME such as `class FOO : public Base` alone). C++-only, so C's heavier `struct TAG var;` never reaches it. The #946 drop stays as the fallback for any residual misparse the blanking doesn't catch. Validated on google/leveldb (LEVELDB_EXPORT, 134 files): class/struct nodes 266→293, extends edges 292→359, phantom functions 588→513; every export-macro real definition flips function→class and `EnvWrapper extends Env` goes absent→present. 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
2b256b93e5
commit
e596c968ab
@@ -209,7 +209,40 @@ function isMacroMisparsedTypeDecl(node: SyntaxNode): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blank an export/visibility macro in a `class/struct EXPORT_MACRO Name …`
|
||||
* *definition* header before parsing. Not knowing the macro, tree-sitter reads
|
||||
* `class EXPORT_MACRO` as an elaborated type specifier and the rest as a
|
||||
* function, so the whole class — its name, base clause, and members — drops out
|
||||
* of the index (#946 catches the resulting phantom function but can't recover
|
||||
* the class), which silently breaks type-hierarchy / inheritance-impact queries
|
||||
* for effectively every Unreal-Engine (`*_API`), Qt/Boost (`*_EXPORT`), LLVM
|
||||
* (`*_ABI`), … class. Replacing the macro with equal-length spaces preserves
|
||||
* every byte offset (and thus line/column), so the declaration then parses as a
|
||||
* normal class_specifier and the existing extraction emits the node, members,
|
||||
* and `extends` edge. (#1061, follow-up to #946.)
|
||||
*
|
||||
* Matched tightly so it can't touch the same macro used as an ordinary value
|
||||
* elsewhere (`int x = SOME_API;`): the macro is the ALL-CAPS token sitting
|
||||
* *between* `class`/`struct` and the type name, and the trailing `[:{]`
|
||||
* definition-guard fires only when a base clause or body follows — the only
|
||||
* shape that misparses. That guard also leaves elaborated-type variable
|
||||
* declarations (`struct FOO var;`, `class FOO obj = …`) untouched, since those
|
||||
* end in `;` / `=` / `[`, never `:` / `{`. C++-only (wired into cppExtractor),
|
||||
* so C's heavier use of `struct TAG var;` never reaches it.
|
||||
*/
|
||||
export function blankCppExportMacros(source: string): string {
|
||||
if (source.indexOf('class') === -1 && source.indexOf('struct') === -1) return source;
|
||||
return source.replace(
|
||||
/\b(class|struct)(\s+)([A-Z][A-Z0-9_]+)(?=\s+[A-Za-z_]\w*(?:\s+final)?\s*[:{])/g,
|
||||
(_m, kw, ws, macro) => kw + ws + ' '.repeat(macro.length)
|
||||
);
|
||||
}
|
||||
|
||||
export const cppExtractor: LanguageExtractor = {
|
||||
// Recover macro-annotated class/struct definitions (`class MYMODULE_API Foo : Base`)
|
||||
// that tree-sitter otherwise misparses into a phantom function (#1061/#946).
|
||||
preParse: blankCppExportMacros,
|
||||
functionTypes: ['function_definition'],
|
||||
classTypes: ['class_specifier'],
|
||||
methodTypes: ['function_definition'],
|
||||
@@ -262,7 +295,9 @@ export const cppExtractor: LanguageExtractor = {
|
||||
const cppKeywords = ['switch', 'if', 'for', 'while', 'do', 'case', 'return'];
|
||||
if (cppKeywords.includes(name)) return true;
|
||||
// `class MACRO Name : public Base { … }` misparses to a function_definition
|
||||
// named after the class — drop that phantom (#946).
|
||||
// named after the class. `blankCppExportMacros` (preParse) recovers the
|
||||
// common ALL-CAPS export-macro shape; this drop is the fallback for any
|
||||
// residual misparse it doesn't blank — still no phantom function (#1061/#946).
|
||||
return isMacroMisparsedTypeDecl(node);
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
|
||||
Reference in New Issue
Block a user