Skip to content

feat(csharp): unify partial-class parts for member and base resolution#732

Open
vitali87 wants to merge 3 commits into
mainfrom
feat/csharp-partial-classes
Open

feat(csharp): unify partial-class parts for member and base resolution#732
vitali87 wants to merge 3 commits into
mainfrom
feat/csharp-partial-classes

Conversation

@vitali87

Copy link
Copy Markdown
Owner

What

Third and final C# follow-up gap. A partial type split across files becomes N path-distinct Class nodes (proj.A.N.Widget, proj.B.N.Widget); they are one logical type. This PR unifies the parts for call resolution so a typed receiver binds to members and bases declared on any part, not just the one it happened to resolve to.

Why it's needed

Unique-named cross-part calls already resolve via the generic name-only fallback, but that fallback is ambiguous (and silently mis-binds) when a method name is shared. Probing var w = new Widget(); w.Beta(); where Beta exists on both a Widget part and an unrelated Other class showed the call binding to Other.Beta — a real mis-resolution. Typed partial-group resolution fixes it.

How

  • Detection + grouping (ingestion): a class carrying the partial modifier is grouped by its namespace-qualified name into a shared list (DefinitionProcessor.csharp_partial_groups: each part qn → the shared list of all part qns), threaded by reference into the type-inference engine (same pattern as class_field_types).
  • _type_name_to_qn: when a type name resolves to several candidates that are all parts of one partial group, return one part (a single logical type) instead of bailing on ambiguity.
  • _find_method: for a partial class, the arity-first-then-name search spans every part and each part's bases, so a member/base declared on another part binds.

Non-partial classes are unaffected (.get(qn) or [qn]). Single-node collapse (deduping the Class node, merging INHERITS/IMPLEMENTS onto one node) is deliberately left as Roslyn-follow-up per the plan — exact symbol-identity merge needs the semantic model; this PR delivers the resolution-level member merge.

Tests (red → green)

test_csharp_partial_classes.py: a member on another part resolved through a typed receiver (with an Other.Beta decoy so only cross-part typing can bind it — verified RED: it bound to Other.Beta before the fix), and an inherited member reached through a base declared on the other part.

Full suite: 5022 passed, 10 skipped. ruff + ty clean.

Independent of #728 and #731.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements support for C# partial class member unification, ensuring that when a class is split across multiple files using the partial modifier, its parts are grouped together. This allows typed receiver resolution to correctly span all parts of the partial class (and their respective base classes) when resolving members and bases. A high-severity issue was identified in the slicing logic used to generate the grouping key when a class is defined in the global namespace (i.e., when module_qn is empty), which could lead to incorrect grouping of unrelated classes. A suggestion was provided to handle empty module_qn values correctly.

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.

Comment on lines +792 to +796
if cs.TS_CSHARP_MODIFIER_PARTIAL in modifiers:
key = class_qn[len(module_qn) + 1 :]
group = self._csharp_partial_index.setdefault(key, [])
group.append(class_qn)
self.csharp_partial_groups[class_qn] = group

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If a C# class is defined in the global namespace (i.e., outside of any namespace declaration), module_qn will be empty (""). In this case, len(module_qn) + 1 evaluates to 1, causing class_qn[1:] to slice off the first character of the class name. This can lead to incorrect grouping of unrelated classes (for example, Widget and Fidget would both resolve to the key "idget" and be grouped together as parts of the same partial class). Checking if module_qn is non-empty before slicing prevents this issue.

Suggested change
if cs.TS_CSHARP_MODIFIER_PARTIAL in modifiers:
key = class_qn[len(module_qn) + 1 :]
group = self._csharp_partial_index.setdefault(key, [])
group.append(class_qn)
self.csharp_partial_groups[class_qn] = group
if cs.TS_CSHARP_MODIFIER_PARTIAL in modifiers:
key = class_qn[len(module_qn) + 1 :] if module_qn else class_qn
group = self._csharp_partial_index.setdefault(key, [])
group.append(class_qn)
self.csharp_partial_groups[class_qn] = group

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the latest push. The partial-group key is now the declaring DIRECTORY (module_qn minus the file stem) plus the namespace-qualified name, so two independent projects that both declare N.Widget live in different directories and are not merged across the assembly boundary. Parts in different directories of one project fall back to generic resolution (safe under-merge) rather than risk a cross-project wrong edge. Added test_same_name_partial_in_different_projects_not_merged (two project dirs + a decoy so the generic fallback can't mask it; verified RED before, GREEN after).

@greptile-apps

greptile-apps Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds C# partial-class unification for typed call resolution. The main changes are:

  • Groups partial class parts by directory-scoped qualified name during ingestion.
  • Threads the shared partial-group map into the C# type inference engine.
  • Resolves typed receivers, fields, members, and inherited methods across all parts of a partial class.
  • Adds tests for cross-part member calls, inherited calls, field receivers, and avoiding cross-project merges.

Confidence Score: 5/5

Safe to merge with minimal risk.

The change is focused on C# partial-class resolution, preserves shared mutable maps correctly, and includes tests for the affected call-resolution paths.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Verified the UV-sync setup completed successfully with the specified group and options, and EXIT_CODE: 0.
  • Ran the direct pytest for the partial-C# tests; the fixture setup failed with AttributeError: module 'codebase_rag' has no attribute 'cli', and the run ended with EXIT_CODE: 1.
  • Applied the runtime workaround and re-ran the tests; four tests passed, including test_typed_receiver_binds_member_on_other_part, test_typed_receiver_binds_inherited_via_other_part, test_field_typed_receiver_sees_field_on_other_part, and test_same_name_partial_in_different_projects_not_merged, with EXIT_CODE: 0.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
codebase_rag/parsers/class_ingest/mixin.py Records C# partial class parts into shared directory-scoped groups while preserving existing field-type ingestion.
codebase_rag/parsers/csharp/type_inference.py Extends C# receiver, field, member, and base resolution to span all parts of a partial class group.
codebase_rag/parsers/type_inference.py Threads C# partial-group state through the generic type inference engine into the C# resolver.
codebase_rag/tests/test_csharp_partial_classes.py Adds tests for cross-part member, inherited method, field receiver, and cross-project non-merge behavior.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Ingest as DefinitionProcessor/ClassIngest
participant Groups as csharp_partial_groups
participant TypeInf as TypeInferenceEngine
participant CSharp as CSharpTypeInferenceEngine
participant Registry as FunctionRegistry

Ingest->>Ingest: "Detect `partial` modifier on C# class"
Ingest->>Groups: Add class QN to shared directory-scoped group
TypeInf->>CSharp: Pass shared partial-group map by reference
CSharp->>Groups: Resolve receiver type to a partial group member
CSharp->>Registry: Search methods on every part and each part's bases
Registry-->>CSharp: Return matching member/base method QN
Loading
%%{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 Ingest as DefinitionProcessor/ClassIngest
participant Groups as csharp_partial_groups
participant TypeInf as TypeInferenceEngine
participant CSharp as CSharpTypeInferenceEngine
participant Registry as FunctionRegistry

Ingest->>Ingest: "Detect `partial` modifier on C# class"
Ingest->>Groups: Add class QN to shared directory-scoped group
TypeInf->>CSharp: Pass shared partial-group map by reference
CSharp->>Groups: Resolve receiver type to a partial group member
CSharp->>Registry: Search methods on every part and each part's bases
Registry-->>CSharp: Return matching member/base method QN
Loading

Reviews (3): Last reviewed commit: "fix(csharp): scope partial-class groupin..." | Re-trigger Greptile

@vitali87

Copy link
Copy Markdown
Owner Author

@greptile review

Comment thread codebase_rag/parsers/class_ingest/mixin.py Outdated
@vitali87

Copy link
Copy Markdown
Owner Author

@greptile review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant