Skip to content

fix(incremental): keep C# extension methods of unchanged files - #3295

Merged
vitali87 merged 1 commit into
mainfrom
claude/csharp-extension-rehydrate-3275
Oct 10, 2026
Merged

vitali87 merged 1 commit into
mainfrom
claude/csharp-extension-rehydrate-3275

Conversation

@vitali87

@vitali87 vitali87 commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Problem: in C#, every call to an extension method declared in an unchanged file lost its CALLS edge as soon as an incremental sync re-parsed the calling file. That covers b.Describe() with static string Describe(this Box b) and services.AddMyServices(). A fresh index binds them exact; after a comment-only edit to the caller there was no edge at all. On serilog/serilog, 21 CALLS edges into Extensions.LiteralValue existed only in a fresh graph.
  • Cause: recv.Ext() is resolved by _find_extension_method from csharp_extension_methods, a name → [(qn, receiver type, namespace, arity)] index filled only while the declaring class is parsed. _rehydrate_registry_from_graph restored the method into the registry but never into that index, and the receiver type and declaring namespace were not on the node to restore it from.
  • Fix:
    • index_extension_method is split into extension_method_facts, which computes the entry from the declaration, and add_extension_method, which adds it to the index. Both the parse and the rehydration use the latter, so the two produce the same tuples.
    • ingest_method stores the facts on C# extension Method nodes: extension_receiver, extension_namespace when not empty, and extension_receiver_arity when not zero. That covers the class-member pass and the #if-truncation recovery.
    • CYPHER_ALL_DEFINITION_QNS returns the three properties, and _rehydrate_definition_row, which only reaches definitions the run did not parse, adds the entry back.
    • The properties are declared on the Method schema (graph-schema.md regenerated), declared unexported in the protobuf parity list with their reason, and returned by the eval graph double.

Demo

C# extension methods across an incremental sync: main vs this branch

The GIF plays the issue's csext repro step by step against Memgraph.

  • Repo: BoxExtensions.cs declares Describe(this Box b) and AddMyServices(this Services s), and Program.Run calls both.
  • Steps: a fresh index, callees Program.Run, a comment-only edit to Program.cs committed, an incremental cgr start --update-graph, and callees again.
  • main: fresh BoxExtensions.AddMyServices (exact) and BoxExtensions.Describe (exact), then [] after the sync.
  • This branch: the same two exact callees before and after the sync. The new tests pass.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement

Related Issues

Fixes #3275

Test Plan

  • All existing unit tests pass (make test): related suites run, full suite see below
  • New tests added for new functionality
  • Integration tests pass (if applicable)
  • Manual testing performed

Red/green TDD in codebase_rag/tests/test_incremental_csharp_extension_methods.py: a fresh index and then an incremental run, through the stateful graph double. The fixture adds generic twins Builder / Builder<T>, each with its own Build extension.

Checklist

  • My code follows the Conventional Commits format
  • I have run pre-commit hooks locally: ruff, ruff format and ty were run by hand on the changed files
  • No hardcoded strings (all in constants or StrEnum)
  • No # type: ignore, cast(), Any, or object type hints
  • Comments explain why, not what

🤖 Generated with Claude Code

https://claude.ai/code/session_01EbsCWQcSTGQXv3g5sk418m


Generated by Claude Code

`b.Describe()` binds to `static string Describe(this Box b)` through
`csharp_extension_methods`, which only a parse of the declaring class
fills. An incremental run that re-parsed the caller alone found no
extension and dropped the call (serilog: 21 edges into
`Extensions.LiteralValue`).

Each extension method's index entry -- receiver type, declaring
namespace, receiver generic arity -- is now stored on its Method node
at ingest and added back to the index when the run rehydrates a
definition it did not parse.

Fixes #3275

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EbsCWQcSTGQXv3g5sk418m
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-09T19:40:16.182172Z 3b2f564 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: vitali87/code-graph-rag/.coderabbit.yaml
  • Review profile: QUIET
  • Plan: Advanced
  • Run ID: 8be4fa4d-f2cf-40c8-ae00-52ef8798a7e3

📥 Commits

Reviewing files that changed from the base of the PR and between 87a8f58 and 3b2f564.


📒 Files selected for processing (9)
  • codebase_rag/constants/graph.py
  • codebase_rag/graph_updater.py
  • codebase_rag/parsers/csharp/utils.py
  • codebase_rag/parsers/utils.py
  • codebase_rag/tests/test_incremental_csharp_extension_methods.py
  • codebase_rag/tests/test_protobuf_node_property_parity.py
  • codebase_rag/types_defs.py
  • docs/architecture/graph-schema.md
  • evals/cgr_graph.py

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 2 remain after this review.



📝 Walkthrough

Walkthrough

The change stores C# extension-method receiver type, declaring namespace, and receiver generic arity as method properties. Definition queries return these values, and incremental rehydration uses them to restore the C# extension-method index. Regression tests compare incremental indexing with fresh indexing.

Changes

C# Extension Method Rehydration

Layer / File(s) Summary
Extension-method metadata
codebase_rag/parsers/csharp/utils.py, codebase_rag/parsers/utils.py, codebase_rag/constants/graph.py, codebase_rag/types_defs.py
C# extension-method receiver type, namespace, and arity are extracted and added to method properties. The graph keys and method schema include these fields.
Incremental metadata rehydration
codebase_rag/constants/graph.py, codebase_rag/graph_updater.py, evals/cgr_graph.py, codebase_rag/tests/test_protobuf_node_property_parity.py, docs/architecture/graph-schema.md
Definition queries return extension-method metadata, and rehydration restores matching entries to the C# extension-method index. The evaluation query and graph-schema documentation include the metadata. The property-parity test excludes these fields from exported properties.
Incremental indexing regression tests
codebase_rag/tests/test_incremental_csharp_extension_methods.py
Tests compare extension-method call edges and index entries after incremental indexing with fresh-index results.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant IncrementalSync
  participant DefinitionQuery
  participant GraphUpdater
  participant CSharpExtensionIndex
  IncrementalSync->>DefinitionQuery: Fetch definition rows with extension metadata
  DefinitionQuery->>GraphUpdater: Return receiver, namespace, and arity
  GraphUpdater->>CSharpExtensionIndex: Restore extension-method entry
Loading

Suggested reviewers: claude


Merge Risk: ⚪ Minimal · up to 3b2f5

An incremental sync now keeps C# extension-method call edges for files that were not reparsed. No merge-blocking risk was identified in the supplied changes.

Pre-merge checks | Passed 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check Passed Issue #3275 requires incremental sync to retain calls to C# extension methods declared in unchanged files. ingest_method stores the receiver, namespace, and arity on extension Method nodes. `_rehy…
Out of Scope Changes check Passed The changes are limited to extension-method indexing and rehydration, the graph schema and protobuf parity declarations for the required metadata, the eval graph double, and regression tests. Each cha…
Title check Passed The title clearly and concisely describes the primary fix: preserving C# extension methods from unchanged files during incremental indexing.
Description check Passed The description follows the repository template and provides a clear summary, demo, change type, related issue, detailed test plan, results, and checklist status.


✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR

🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR


  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium impact] Safe to merge within the reviewed scope. Focused execution confirms that incremental indexing preserves extension calls and agrees with fresh indexing after declaration changes.

T-Rex evidence

Evidence from the check

  • The executed shell script prepares the parent checkout and runs identical focused checks against parent and PR code, making the comparison reproducible.

Evidence from the check

  • The executed test source compares calls and binding facts for caller-only, declaration-only, and combined edits, rejecting stale receiver bindings.

Evidence from the check

  • This copy preserves the existing focused suite executed on both revisions, showing the assertions behind the regression results.

Command output from the check

  • The parent revision ran all six checks and failed three caller-only preservation and rehydration checks, reproducing the original defect.

Command output from the check

  • The PR revision ran all six checks with printed fresh and incremental calls and facts, confirming all six pass.

Command output from the check

  • This capture records the exact shell invocation and both revision runs, preserving the complete executed comparison.

Command output from the check

  • The requested first command exited 127 because the virtualenv interpreter target was absent, identifying an environment blocker rather than a code failure.

Command output from the check

  • The executed uv command restored Python 3.12.15 successfully, enabling reuse of the installed virtualenv dependencies.

Command output from the check

  • The requested focused pytest command ran successfully after interpreter recovery, confirming all three existing tests pass.

Evidence from the check

  • This executed source preserves the initial assertion that rejected a fresh-index heuristic fallback, explaining why that assertion was corrected.

Command output from the check

  • The first parent run captured the original regression and the overly strict fallback assertion failures, preserving the initial observations.

Command output from the check

  • The first PR run showed matching fresh and incremental heuristic fallback edges despite two authored assertion failures, demonstrating those failures were not PR defects.

View artifacts

Summary

The PR saves C# extension-method facts on Method nodes and restores them for unchanged files during incremental indexing.

  • C# extension calls survive incremental updates to their callers.

Integration note: vitali87 acknowledged that PR #3281 also adds Method properties and columns to CYPHER_ALL_DEFINITION_QNS; whichever PR merges second must retain both sets of additions.

Reviews (1) · Last reviewed commit: "fix(incremental): keep C# extension meth..." · Reviewed by Greptile

vitali87 commented Oct 9, 2026

Copy link
Copy Markdown
Owner Author

Heads-up: this says Fixes #3275, but #3275 also has a Dart half that this C#-only change doesn't touch. If the PR closes the issue as is, that half gets lost. From the two Dart comments there, both still reproduce on this branch's base (main @ 87a8f58):

  1. dart_extension_on_types (extension class → on type) is never rehydrated. b.grow() on a Box, with extension BoxGrow on Box in an unchanged file, loses its edge after an edit to the caller. The Class node doesn't store the on type, so it needs a node property like the C# extension_receiver added here.
  2. dart_exposed_libraries (export 'x.dart'; / part hand-ons) is never rehydrated either. For an extension reached through a barrel library, both indexes have to be restored. With only one of them the edge stays lost.

Dart barrel case: lib/shapes.dart exports src/circle.dart and ext/fancy.dart; report.dart calls c.label(); fresh index binds fancy.Fancy.label exact, and after a comment-only edit to report.dart there are no callees

The repros are in the #3275 comments. Using Refs #3275 here and leaving the issue open for Dart, or splitting the Dart half into its own issue, would keep it tracked.


Generated by Claude Code

@sonarqubecloud

sonarqubecloud Bot commented Oct 9, 2026

Copy link
Copy Markdown

@vitali87
vitali87 merged commit 9f9082d into main Oct 10, 2026
33 checks passed
@vitali87
vitali87 deleted the claude/csharp-extension-rehydrate-3275 branch October 10, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

1 participant