fix(cpp): eliminate remaining dangling edges via pass-2 location reuse and deferred INHERITS resolution#663
Conversation
…e INHERITS resolution
…r cross-file INHERITS resolution, dedupe template function nodes, and require METHOD targets for overrides
There was a problem hiding this comment.
Code Review
This pull request addresses C++ parsing issues (such as phantom callers and duplicate template function nodes) by deferring C++ inheritance resolution until all classes are registered, tracking function locations during the definition pass, and avoiding duplicate template function registrations. The review feedback highlights two important issues: a missing import of create_inheritance_relationship (or rel) in mixin.py that will cause a runtime NameError, and a potential lookup failure in parent_extraction.py if a C++ base class name contains spaces before template angle brackets, which can be resolved by stripping whitespace.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Greptile SummaryThis PR fixes several C++ graph ingestion paths that produced dangling edges. The main changes are:
Confidence Score: 5/5This PR is safe to merge with low risk. The changes are focused on C++ graph ingestion edge resolution and include tests for the main clean-index and incremental scenarios reviewed. No blocking or non-blocking issues were identified. No files require special attention.
What T-Rex did
Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant P2 as Pass 2 DefinitionProcessor
participant Reg as Function registry / lookups
participant Defer as Deferred C++ inheritance queue
participant P3 as Pass 3 CallProcessor
participant Graph as Graph ingestor
P2->>Reg: Register C++ classes/functions/methods
P2->>Reg: Store cpp_function_locations by module/start line
P2->>Defer: Queue C++ base names instead of emitting guessed INHERITS
P2->>Reg: Rehydrate unchanged definitions and paths on incremental runs
Defer->>Reg: Resolve base qns scope-first across registered classes
Defer->>Graph: Emit resolved INHERITS edges only for real nodes
P3->>Reg: Lookup recorded caller location by module/start line
P3->>Graph: Emit CALLS from recorded qn/label
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant P2 as Pass 2 DefinitionProcessor
participant Reg as Function registry / lookups
participant Defer as Deferred C++ inheritance queue
participant P3 as Pass 3 CallProcessor
participant Graph as Graph ingestor
P2->>Reg: Register C++ classes/functions/methods
P2->>Reg: Store cpp_function_locations by module/start line
P2->>Defer: Queue C++ base names instead of emitting guessed INHERITS
P2->>Reg: Rehydrate unchanged definitions and paths on incremental runs
Defer->>Reg: Resolve base qns scope-first across registered classes
Defer->>Graph: Emit resolved INHERITS edges only for real nodes
P3->>Reg: Lookup recorded caller location by module/start line
P3->>Graph: Emit CALLS from recorded qn/label
Reviews (3): Last reviewed commit: "fix(cpp): never emit self-INHERITS from ..." | Re-trigger Greptile |
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
@greptile review |
|
@greptile review |
|




Iteration 2 of #652. Eliminates every remaining dangling edge on the souffle corpus: 4,261 after #657, now 0 (campaign total 18,175 -> 0). Zero orphans preserved, suite 4593 passed / 0 failed, ty and ruff clean.
Root causes fixed (each red -> green in commit history)
1. Residual phantom callers (3,683 CALLS FROM + 157 REFERENCES FROM + 21 INSTANTIATES FROM)
Pass 3 re-derived caller qns structurally and diverged from Pass 2 on preprocessor-distorted class bodies. souffle's
BTreeDelete.hdeclares methods inside#ifdef IS_PARALLEL/#elseblocks inside a template class; tree-sitter's recovery drops parts of the ancestry, so Pass 2 registered...engine.node.splitwhile Pass 3 attributed the same body's calls to...node.splitor...split.Fix: Pass 2 now records every C++ Function/Method node it registers in
cpp_function_locationskeyed by(module_qn, start_line)(value: label, qn, container qn). Pass 3 consults the record in both walk paths (_process_calls_in_functions,_process_methods_in_class) before deriving anything. This is the record-and-reuse pattern from #657's out-of-class method fix, generalized to all C++ callers.ingest_methodnow returns the final method qn so call sites can record it.2. Cross-file INHERITS bases (398 TO-missing)
parse_cpp_base_classesanchored every base to the child's own module qn at parse time with no resolution, soclass Aggregator : public Argument(Argument defined in Argument.h) pointed at a phantom under Aggregator.h's module.Fix: C++ INHERITS emission is deferred (
DeferredCppInherit) until every class is registered, then resolved scope-first across files via_resolve_cpp_class_qn(newexclude_qnparameter preventsclass Type : public other::Typefrom self-inheriting). Resolved qns replace the parse-time guesses inclass_inheritancein place, so Pass-3 method resolution and override detection walk the real hierarchy - the run now finds cross-file overrides it previously missed. A base that resolves nowhere (std::exception) emits no edge rather than one the database silently drops.3. Template function duplicate nodes
The C++ functions query captures a templated free function twice: the
template_declarationwrapper and its innerfunction_definition. Both registered, minting aqn@lineduplicate node per template function; with fix 1 in place, call attribution faithfully bound bodies to the duplicate. The inner definition no longer registers (mirroring the existing class-specifier rule); the wrapper's location is also recorded at the inner definition's start line so Pass 3's walk still hits it.4. OVERRIDES onto a same-named nested class (final 4)
check_method_overridesmatched any registry entry namedparent_qn.method_namewithout checking its node type. A nested class that inherits its encloser (souffle'snode::inner_node : node) has a constructor whose name equals the nested class registered at exactly that qn, so the ctor emitted OVERRIDES with a Method label onto a Class node. The parent member must now beNodeType.METHOD.Test changes
test_cpp_preproc_caller_attribution.py,test_cpp_cross_file_inherits.py,test_cpp_template_function_single_node.py,test_cpp_ctor_class_override_false_positive.pytest_cpp_conceptsdefinitions floor 5 -> 4 (the old floor counted a DEFINES edge onto a now-removed template duplicate node)Verification
-n auto)ty check: same 352 pre-existing diagnostics as main, none new;ruff checkandruff formatclean