fix(cpp): extract templated and include-guarded declarations from C/C++ headers#535
Open
vitorvalecsw wants to merge 2 commits into
Open
fix(cpp): extract templated and include-guarded declarations from C/C++ headers#535vitorvalecsw wants to merge 2 commits into
vitorvalecsw wants to merge 2 commits into
Conversation
The C/C++ structural walker (walkTopLevel) only descended into namespace bodies, so two very common header constructs were silently skipped: - Include guards: `#ifndef __X_H__ ... #endif` wraps a header's contents in a `preproc_ifdef` node that was never traversed, so every header using an include guard yielded zero symbols. - Templates: `template <...> class/struct/function` parses as a `template_declaration` wrapping the entity — at file/namespace scope and as class members. These were skipped, dropping all templated classes, structs, free functions, and templated member methods. Recurse into preproc_ifdef/if/else/elif blocks, and unwrap template_declaration nodes to the inner class/struct/function (both top-level and inside class bodies). Adds regression tests: include guards, top-level and namespaced template classes, templated member methods, templated free functions, and a combined real-header shape (guard + namespace + templates). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The extraction script already emits a deterministic callGraph
({caller, callee, lineNumber}) for code files, but the calls-edge instruction
only told the agent to "infer from imports + function names", so the callGraph
went unused and cross-file calls were rarely emitted.
Make the callGraph the primary source for calls edges: for each entry whose
callee resolves to a function/method of an imported/neighbor file, emit a
calls edge; skip standard-library/external callees.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The C/C++ extractor produced empty structural output for most real headers. Two root causes in
walkTopLevel(packages/core/src/plugins/extractors/cpp-extractor.ts), which only descended intonamespacebodies:#ifndef __X_H__ / #define __X_H__ / … / #endif, which tree-sitter parses as apreproc_ifdefnode. The walker never entered it, so every header using aninclude guard yielded zero symbols (classes, functions, includes — all lost).
template <…> class/struct/functionparses as atemplate_declarationwrapping the entity — at file/namespace scope and as aclass member. None of these were unwrapped, so all templated classes, structs,
free functions, and templated member methods were dropped.
Because these two patterns are near-universal in real C++ headers (include guards especially), the extractor’s deterministic output for a typical header-heavy C++ project was effectively empty, forcing the downstream LLM pass to reconstruct everything from raw source.
Fix
preproc_ifdef/preproc_if/preproc_else/preproc_elifblocks inwalkTopLevel, so guard-wrapped declarations (and their#includes) are traversed.template_declarationto its innerclass_specifier/struct_specifier/function_definition, both at top level and inside class member lists.A useful side effect: because
resolveImportsshares the same walker, header→header#includeedges (previously hidden inside guards) now resolve too.Companion change (
agents/file-analyzer.md)The extraction script already emits a deterministic
callGraph({caller, callee, lineNumber}), but thecalls-edge instruction only said “infer from imports + function names”, so the call graph went unused. The instruction now makescallGraphthe primary source forcallsedges (resolving each callee against imported/neighbor files, skipping std/external symbols).Before / After (real C++ headers)
Queue.h(template<class T> class Queue)Queue+enqueue/dequeue/size/clear/…SequentialUnorderedMap.h(template)Uuid.h(guarded, non-template)Uuid+ 12 functionsLogger.h(guarded; variadic template methods)Logger+log/logfmethodsTests
Adds 6 regression tests to
cpp-extractor.test.ts:#ifndefinclude guard,Full core suite: 759/759 passing (
pnpm --filter @understand-anything/core test), including the new tests.Known limitation (out of scope)
Free (non-member) function prototypes at file/namespace scope — e.g.
void freeFn();declared in a header with the body in a.cpp— are still not emitted as nodes from the header (only the.cppdefinition creates the node). Class method declarations are captured. This is a pre-existing, separate limitation from the guard/template bugs fixed here and is comparatively rare in class-centric C++ codebases; left for a follow-up to keep this change focused.Notes
.h→language mapping was intentionally left unchanged (.his ambiguous C vs C++, andCppExtractoralready handles both language ids with the same grammar, so it does not affect extraction).