fix(cpp): resolve includes against real files and unify name resolution across passes#657
Conversation
There was a problem hiding this comment.
Code Review
This pull request resolves issue #652 by significantly improving C++ parsing accuracy, particularly for include resolution, C++17 nested namespaces, out-of-class method bindings, and operator calls. It ensures same-stem headers and source files are correctly disambiguated, nested namespaces are split properly, out-of-class methods are resolved scope-first and reused deterministically, and user-defined operator overloads are prioritized over builtins. The review feedback suggests replacing os.path.commonprefix with os.path.commonpath in _resolve_cpp_include_target to ensure path-safe, component-aware matching when resolving include targets.
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 makes C++ graph name resolution use the same registered entities across passes. The main changes are:
Confidence Score: 5/5Safe to merge with minimal risk. No blocking or non-blocking issues were identified in the changed paths. The updated C++ resolution flow is covered by focused tests for the affected graph edge families. No files require special attention.
What T-Rex did
Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Def as DefinitionProcessor
participant Imp as ImportProcessor
participant Reg as FunctionRegistry
participant Call as CallProcessor
participant Graph as Ingestor
Def->>Graph: create MODULE / CLASS / METHOD nodes
Def->>Def: disambiguate module qns and record C++ method bindings
Imp->>Imp: resolve quoted includes against real repo files
Imp->>Graph: create IMPORTS edges / ExternalModule nodes
Call->>Def: reuse module qn map and recorded method bindings
Call->>Reg: require registered class/operator targets
Call->>Graph: emit CALLS from registered caller/callee qns
%%{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 Def as DefinitionProcessor
participant Imp as ImportProcessor
participant Reg as FunctionRegistry
participant Call as CallProcessor
participant Graph as Ingestor
Def->>Graph: create MODULE / CLASS / METHOD nodes
Def->>Def: disambiguate module qns and record C++ method bindings
Imp->>Imp: resolve quoted includes against real repo files
Imp->>Graph: create IMPORTS edges / ExternalModule nodes
Call->>Def: reuse module qn map and recorded method bindings
Call->>Reg: require registered class/operator targets
Call->>Graph: emit CALLS from registered caller/callee qns
Reviews (3): Last reviewed commit: "chore: refresh uv.lock after rebase onto..." | Re-trigger Greptile |
|
@greptile review |
…calization, and caller attribution
…, and method resolution across passes
b2daae3 to
daebde8
Compare
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
|
@greptile review |



Part of #652 (first iteration of the dangling-edge campaign).
Result
souffle-lang/souffle emitted-edge audit: 18,175 dangling edges down to 4,261 (77% reduction). The IMPORTS family (3,908) and the builtin-operator CALLS family (~1,700) are eliminated entirely; the phantom-caller CALLS family drops from 11,353 to 3,683. The #650 invariant holds: zero orphan nodes, zero schema violations on the re-indexed graph.
Root causes fixed
All five are the same disease in different organs: independent components deriving names their own way, with every disagreement becoming an edge the database silently drops.
#include "Directive.h"was resolved as project-root + path with the extension stripped (also mangling.hppstems via.replace(".h", "")), so same-stem header/source pairs produced self-imports and-I-style includes (ast/Directive.h) got phantom roots. Includes now resolve against the actual file tree (includer-relative, root-relative, then unique path-suffix match with longest-common-prefix tie-break) using the collision-disambiguation replay already proven in the libclang frontend (build_module_qn_map). Unresolvable quoted includes are external headers, not phantom project modules.ExternalModulenodes for external targets (mirroring feat(graph)!: dedicated ExternalModule label replaces Module.is_external #597), instead of emitting relationships against nothing.resolve_class_name. Pass-3 resolution now requires the answer to be a registered entity (require_registered), falling through to the registry-backed steps; Pass-2 semantics unchanged.CallProcessor._module_qnusedwith_suffix(""), so every caller inside a same-stem header was attributed under the source file's qn. It now consults the definition pass'smodule_qn_to_file_pathas single source of truth.namespace a::b {rendered two ways. The namespace walk kept the literala::bsegment while other paths split it, so one class got two nodes (the forward-declaration dedup missed across spellings) and resolution picked the variant without the methods. Canonicalized to dotted segments inextract_namespace_pathand_cpp_get_name; modern and classic nesting now yield byte-identical qns (also aligning tree-sitter with the libclang frontend's nested cursors).ast::Typeandast::analysis::Type), and Pass 3 re-resolved independently, diverging. The definition pass now resolves once (namespace-scoped candidates first, deterministic sorted iteration), records each binding keyed by (module qn, definition line), and call attribution replays the recorded decision.operator+never resolved because the syntheticbuiltin.cpp.operator_*table answered first, and those synthetic targets never had nodes. User overloads now win; primitive builtin operators emit no call edge (six fixture-count tests updated: they were counting edges the database always dropped).Verification
Remaining (next iterations, tracked in #652)
3,683 CALLS + 157 REFERENCES phantom callers, 398 INHERITS cross-file base resolution, 21 INSTANTIATES, 2 OVERRIDES.