feat: register C/C++ and Rust macro definitions as Function nodes with resolvable invocations#672
Conversation
…h resolvable invocations
|
@greptile review |
There was a problem hiding this comment.
Code Review
This pull request adds support for C/C++ and Rust macro definitions and instantiations, registering them as Function nodes and resolving their call sites appropriately. It also handles namespace separation in Rust to prevent false bindings between functions and macros, and adds comprehensive tests. The review feedback highlights a few robust improvements: skipping tree-sitter comment nodes when detecting Rust macro exports, guarding against potential null spelling values in Clang cursors, and using safe dictionary lookups for node properties to avoid KeyErrors.
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 adds macro definitions and invocations to the graph model. The main changes are:
Confidence Score: 5/5Safe to merge with minimal risk. No blocking issues were found in the changed paths. The PR includes focused coverage for Rust macro definitions, invocation resolution, namespace separation, exported macros, incremental rehydration, and C++ frontend macro calls. No files require special attention.
What T-Rex did
Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Parser as Rust/libclang parser
participant Def as Definition ingest
participant Reg as Function registry + macro_qns
participant Calls as Call processor/frontend flush
participant Graph as Graph ingestor
Parser->>Def: Capture macro definition
Def->>Reg: Register Function qn with is_macro
Def->>Graph: MERGE Function and DEFINES edge
Parser->>Calls: Capture macro invocation
Calls->>Reg: Resolve target and validate macro namespace
Calls->>Graph: Emit CALLS edge to macro Function
%%{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 Parser as Rust/libclang parser
participant Def as Definition ingest
participant Reg as Function registry + macro_qns
participant Calls as Call processor/frontend flush
participant Graph as Graph ingestor
Parser->>Def: Capture macro definition
Def->>Reg: Register Function qn with is_macro
Def->>Graph: MERGE Function and DEFINES edge
Parser->>Calls: Capture macro invocation
Calls->>Reg: Resolve target and validate macro namespace
Calls->>Graph: Emit CALLS edge to macro Function
Reviews (3): Last reviewed commit: "fix: rehydrate macro namespace on increm..." | Re-trigger Greptile |
…tadata, and frontend docstring
Greptile SummaryThis PR registers C/C++ and Rust macro definitions as Function nodes and resolves macro invocations. The main changes are:
Confidence Score: 4/5Mostly safe, with one contained indexing bug to fix before relying on incremental Rust macro results. Clean-index macro handling is covered by focused tests. Incremental indexing can drop calls to unchanged Rust macros because macro namespace metadata is not rehydrated. codebase_rag/parsers/call_processor.py; codebase_rag/graph_updater.py rehydration metadata for macro definitions
What T-Rex did
Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Def as Definition Pass
participant Reg as Function Registry
participant MacroSet as macro_qns
participant Calls as Call Pass
participant Graph as Graph Ingestor
Def->>Reg: "register macro_rules! / #define as Function"
Def->>MacroSet: record Rust macro qns
Calls->>Calls: extract macro invocation target
Calls->>Reg: resolve call name
Calls->>MacroSet: validate Rust macro/function namespace
Calls->>Graph: emit CALLS edge to macro Function
Note over Graph: C/C++ frontend emits macro definitions and instantiations directly
%%{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 Definition Pass
participant Reg as Function Registry
participant MacroSet as macro_qns
participant Calls as Call Pass
participant Graph as Graph Ingestor
Def->>Reg: "register macro_rules! / #define as Function"
Def->>MacroSet: record Rust macro qns
Calls->>Calls: extract macro invocation target
Calls->>Reg: resolve call name
Calls->>MacroSet: validate Rust macro/function namespace
Calls->>Graph: emit CALLS edge to macro Function
Note over Graph: C/C++ frontend emits macro definitions and instantiations directly
Prompt To Fix All With AIFix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
codebase_rag/parsers/call_processor.py:1758-1760
**Rehydrate macro namespace**
Incremental runs rehydrate unchanged Function nodes into `function_registry` but never repopulate `DefinitionProcessor.macro_qns`. When a changed Rust file calls a `macro_rules!` definition from an unchanged file, resolution returns that macro qn, this check treats it as a non-macro target, and drops the `CALLS` edge. Clean and incremental indexes then disagree, and the unchanged macro can be reported dead.
### Agentic Framework
-... ([source](https://app.greptile.com/graph-code/github/vitali87/code-graph-rag/-/custom-context?memory=d4240b05-b763-467a-a6bf-94f73e8b6859))
Reviews (2): Last reviewed commit: "docs: document macros-as-Function schema..." | Re-trigger Greptile |
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…in macro_export detection
|
@greptile review |
|
|
Re the Codecov report (9 missing lines in cpp_frontend/frontend.py): addressed in #673. Seven lines are now covered by new fixtures (Module-caller fallback for file-scope macro uses, out-of-repo and ignored-dir use sites, command-line and system macros) plus a guard reorder that removed an unreachable check. The two remaining lines are defensive guards against libclang returning None cursors (MACRO_INSTANTIATION with no file / no referenced definition); they are not constructible from source-level fixtures and are kept so unusual TUs skip instead of crash. The SonarQube S3776 issue on _register_function is also fixed there. |
test(cpp): address codecov and sonar reports from #672 (macro guard coverage, complexity)



Summary
Macros are modeled cross-language as Function nodes (the schema decision: no language-specific label). Previously macro definitions were invisible in every language: Rust
macro_invocationsites were captured but had no definition node to bind to, and the C++ libclang frontend never saw preprocessor entities at all. Now C/C++#defineand Rustmacro_rules!register as Functions, invocations resolve to them, and dead-code treats macros like any function.Rust
test_rs_macro_definition_nodes.py):macro_definitionjoinsSPEC_RS_FUNCTION_TYPESand the function query, somacro_rules! squareregisterscrate.module.squareas a Function.square!(3)was captured as a call but dropped nameless --macro_invocationcarries its callee in themacrofield, which_get_call_target_namenever read. A dedicated case extracts it, so the invocation resolves through the normal chain.test_macro_and_fn_namespaces_do_not_cross_bind): Rust macros and functions live in separate namespaces. Without a gate, std-preludewrite!(f, ..)would bind a same-modulefn write(a false edge that revives dead code), andtrace(x)would bind a same-modulemacro_rules! trace. Definition ingest records macro qns (DefinitionProcessor.macro_qns); Pass 3 requires macro-invocation call sites to bind macro targets and fn-namespace call sites to avoid them.#[macro_export](test_macro_export_attribute_marks_exported):macro_rules!takes nopub; the attribute is what publishes a macro (to the crate root) as library API, so it now setsis_exported-- an exported but internally-uninvoked macro must not report dead.C/C++ (libclang frontend)
test_cpp_frontend_macros.py): TUs parse withPARSE_DETAILED_PROCESSING_RECORD; repoMACRO_DEFINITIONcursors become Function nodes with Module DEFINES edges. Compiler builtins (no file), system-header macros (outside the repo), and empty-bodied object-like macros (include guards, feature flags -- extent covers only the name) are skipped.MACRO_INSTANTIATION.referencedgives the exact definition (libclang resolved it). Macro cursors are TU-level preprocessing entities, never AST-nested, so the caller is recovered at flush by tightest enclosing function/method span in the use-site file; a use outside any span attributes to the Module, mirroring the existing module-caller rule.Verification (RED -> GREEN)
anyhow!/bail!/ensure!/cfg-duplicatedbacktrace!and invokes them internally) reports a byte-identical dead set vs main -- all newly-registered macros are live via roots or resolved invocations, zero regressions.