Repository navigation
fix(c,cpp): a struct, class, union or enum defined in a variable declaration is indexed - #2446
Merged
Merged
Conversation
…aration is indexed
`struct Foo { … } foo;`, `class Bar { … } bar;` and `static struct { … } SPT;`
outside any function or class body went to the variable extractor, which
skipped the declaration's children, so the type, its methods and its
enumerators were never indexed in either extraction engine.
Both engines now walk such a type before the variables (C always; C++ when
the tree has no errors), and the declaration's function-as-value scan skips
the walked type, which captured its own. An unnamed struct, union or enum
takes the name of the first variable its declaration declares (also inside
function bodies, where it was `<anonymous>`), as `typedef struct { … } Name;`
already takes the typedef name, and the comment above the declaration is the
type's docstring. cDeclaratorIdentifier moves to languages/c-cpp.ts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-cb4e58 # Conflicts: # CHANGELOG.md # codegraph-kernel/src/ccpp/mod.rs # src/extraction/tree-sitter.ts
…copes in the test header Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-cb4e58 # Conflicts: # CHANGELOG.md
…-cb4e58 # Conflicts: # CHANGELOG.md
…-cb4e58 # Conflicts: # CHANGELOG.md
This was referenced Oct 7, 2026
Merged
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
A class, struct, union or enum defined in the type of a C or C++ variable declaration was never indexed, by either engine:
The walker's
declarationbranch (variableTypes) hands the declaration toextractVariableand skips its children, so a class-liketypewith a body is never visited. The kernel'skind == "declaration" && !inside_class_like()branch mirrors it. Only declarations outside function and class bodies are affected, since inside a body the children walk already reaches the type. #2426 (now on main) added this walk for C++ files whose tree has errors, in brace scopes. Clean files, and C, were still open.Fix
Both engines (
src/extraction/tree-sitter.ts+languages/c-cpp.ts, mirrored incodegraph-kernel/src/ccpp/mod.rs):n::fooandn::bar; C: init, pointer and array declarators, as before). One walk covers every case: C always, and C++ when the tree is clean or the file is walked in fix(cpp): a class keeps its namespaces and enclosing class past code the parser misreads #2426's brace scopes. The only file left out is an erroring C++ file whose braces don't balance (no brace scopes), which fix(cpp): a class keeps its namespaces and enclosing class past code the parser misreads #2426 also leaves alone. There, error recovery can run a class past its own}, and the walked classes would land in the wrong scope (measured below).static struct { … } SPT;givesstruct SPT. This also applies inside function bodies, where such types were indexed as<anonymous>until now. It goes through the c/cppresolveNamehook, mirrored in the kernel'sextract_name_raw.getDeclarationWrapper, mirrored byclass_like_docstring)./** The handler registry. */ struct Registry { … } registry;gives both nodes that docstring.void (*hs[1])() = {&on_open};) is captured once, by the type, not a second time from the file. Main's brace-scope walk captured those twice.cDeclaratorIdentifiermoves fromtree-sitter.tstolanguages/c-cpp.ts, where the naming rule shares it, and fix(cpp): a class keeps its namespaces and enclosing class past code the parser misreads #2426'sCPP_CLASS_LIKE_SPECIFIERScheck becomesisClassLikeDefinitionthere.Naming an unnamed type
No code can name the type of
static struct { … } SPT;; it only ever namesSPT. So the struct is indexed under that name, which is the ruletypedef struct { … } Name;already follows. Before, an agent searching forSPT(redis'ssetproctitle.c) orrdbstate(redis-check-rdb.c) found nothing: C doesn't index a variable with a bare declarator (the macro-prototype rule inextractVariable, unchanged here), and<anonymous>isn't searchable. Nowcodegraph_exploreonSPTreturns the struct and its fields.What stays
<anonymous>: a type whose declaration declares no variable, like a function's return type (struct { int x; } make(void);), and the unnamed types of class members (field_declaration), which this PR doesn't touch.Alternatives considered:
<anonymous>everywhere: consistent, but unsearchable, which defeats the redis-style idiom.__anonNNN: also unsearchable.One side effect: in fmt,
struct { … operator()(…) … } enter_state;is a local functor, so a callenter_state(state::align)now links to the struct namedenter_state. The edge isinstantiates, because its target is a type. That is the right code, though a call to itsoperator()would be the more precise edge. Before, those calls linked to nothing.Verification
New
__tests__/c-cpp-declaration-types.test.ts: 29 tests, on the kernel and on wasm, with LF and CRLF, plus an end-to-end index. It covers:<anonymous>;Registry::sizegets its callers,Worker extends Baseresolves, andsearchNodes('SPT')finds the struct.All 29 fail on main's walker and main's kernel (ed199e6).
torture.candtorture.cppgain the same shapes, so the C/C++ kernel-parity suite keeps the two engines in agreement on them.scripts/kernel-parity.mjs --lang c,cppon jq, leveldb, redis, fmt, protobuf and rocksdb: 0 files with diffs, and deferral counts are unchanged. UnderCODEGRAPH_KERNEL_CCPP_ERROR_EXTRACT=1, the set of files where the engines disagree is identical for main and the fix in all six repos (5 / 4 / 19 / 9 / 56 / 11).npx tsc --noEmit,npm run build,bash scripts/build-kernel.sh. Full suite: passes. The parallel run on this shared box (100% CPU from other sessions) had 109 failures in 39 files, all timing: sync, git, daemon and MCP tests hitting 5 s timeouts orEBUSYon teardown, plus the timed-out cases ofextraction,resolutionandcpp-macro-and-constructor-calls. Rerun serially with 120 s timeouts, those 39 files passed 1634 of 1635. The last one,function-ref's Caller/impact graph misses methods passed as first-class references (callbacks), e.g. executor.submit(obj.method, …) #1820 case, has its own 60 s limit and passed alone in 27 s. Every C/C++ suite passed in the parallel run too, this PR's 29 tests included.Validation: before and after on real repos (main = ed199e6)
Each repo was indexed with main and with this branch, both engines as shipped (kernel for clean C/C++ files, wasm for the rest). The graphs were diffed by node, and by edge keyed by call site.
contains(rename)contains(rename)containsretargeted; 1 removed + 1 added (a renamed enum's members)instantiates;contains+3 / −2 / 2 retargeted; 1 confidence changecalls;contains+1 / 4 retargetedcalls;contains+4 / −3 / 7 retargetedNo
calls,extends,instantiatesorreferencesedge was removed or retargeted in any repo. Every removed site is acontainsedge whose source was renamed from<anonymous>; the same edge reappears under the new name.Types indexed for the first time:
sentinelState(sentinel.c),luaCtxandldbState(eval.c),SPT,rdbstate,redis-cli'sconfig,prefandclusterManager,redis-benchmark'sconfig,RedisModuleForkInfo,redisTest, the ACL tablesACLCategoryItem,ACLUserFlagandACLSelectorFlags,ExprOptable, and indeps/: lua'spriority,native, and tre'stre_macro_structandtre_ctype_map.fstat_simwith its three enumerators, andtest_mock(posix-mock-test.cc).StaticDestructionTesterand its destructor (env_test.cc). Its calls toEnv::Default()andGetHostNameStringnow resolve correctly.ForceShutdownand its destructor (googletest.cc), whose two calls toShutdownProtobufLibraryresolve correctly, and the two utf8_rangeftabstructs.Renamed from
<anonymous>, all function-local:res) and leveldb 1 (cases);enter_state,parse_presentation_type, with theiroperator());Triage of the rest:
instantiates: the functor calls described above. Each now reaches the struct that holds theoperator()that runs; before, nothing.max_size()inmax_size_allocator::allocatestill links tomax_size_allocator::max_size, but at 0.4 instead of 0.9. The newly indexed enum valuefstat_sim::max_size(posix-mock-test.cc) shares its name, and exact-match still counts an enum value as a candidate for a call (pre-existing). The target is unchanged.These site diffs are identical, as sets, to the same A/B run on this branch's original base (d8a7f86), before #2426, #2413 and #2415 landed.
Re-run on main cdb994b (after #2449 changed the C/C++ preParse, and #2430 and #2442 landed): the changed sites are identical, as sets, in all six repos. Parity is still 0 diffs, with fewer files deferred now that more of them parse cleanly: leveldb 9, redis 176, fmt 31, protobuf 292 and rocksdb 214, against 20, 186, 32, 343 and 223 before. This PR's tests,
kernel-ccpp-parity,cpp-brace-scopesand #2449'scpp-attribute-macrospass on the merged tree (100/100, with the #2430 and #2442 test files).Erroring C++ files. Before merging #2426, I measured an unconditional walk with no brace scopes. In leveldb's
util/env_posix.cc, error recovery runsclass Limiterto the end of the file (an#ifinside its constructor's initializer list). That file gained 8 classes and 57 methods, all nested underleveldb::Limiter::. Two protobuf types also landed in a truncated namespace (google::ConstMapIterator). That is why an erroring C++ file without brace scopes is left alone. With #2426, main already indexes those classes in their correct scope, and this branch leaves them unchanged.Overlap
fix(cpp): a class keeps its namespaces and enclosing class past code the parser misreads #2426 (merged) added the same walk for erroring C++ files in brace scopes. This branch merged main and converged on one walk:
CPP_CLASS_LIKE_SPECIFIERScheck becameisClassLikeDefinition;cpp-brace-scopes.test.tspasses unchanged.fix(cpp): a class keeps its members past attribute macros the parser can't read #2449 (merged) rewrote much of the C++ preParse in
languages/c-cpp.ts, which this PR also edits. The two merged without conflict, and the re-run above shows no interaction beyond more files parsing cleanly.Follow-ups (not in this PR)
objc.tsalso listsdeclarationinvariableTypes, and the gate here is C/C++ only.union { … } u;inside a struct) are still<anonymous>. The same naming rule could apply to them, but it renames many existing nodes (89 in redis alone), so it deserves its own change.enter_state(x)) could link to the type'soperator()as a call, instead of to the type as an instantiation.static struct { … } SPT;has novariable SPT, by the existing macro-prototype rule. The struct now carries the name.🤖 Generated with Claude Code