Skip to content

feat(analyzers): detect RPC contract changes - #10337

Draft
ReubenBond wants to merge 2 commits into
dotnet:mainfrom
ReubenBond:recover-rpc-contract-analyzer
Draft

feat(analyzers): detect RPC contract changes#10337
ReubenBond wants to merge 2 commits into
dotnet:mainfrom
ReubenBond:recover-rpc-contract-analyzer

Conversation

@ReubenBond

@ReubenBond ReubenBond commented Aug 3, 2026

Copy link
Copy Markdown
Member

Orleans RPC contracts can change without an explicit record of the prior interface shape, making accidental incompatibilities difficult to catch before rolling upgrades.

This adds a grain interface version analyzer and code fixes which compare source interfaces with a GrainInterfaces.txt contract manifest. It reports undeclared interfaces and members, version mismatches, unretired removals, missing manifests, and duplicate declarations, with focused analyzer coverage for versioning, aliases, generics, inheritance, and code fixes.

Microsoft Reviewers: Open in CodeFlow

Copilot AI review requested due to automatic review settings August 3, 2026 22:51

Copilot AI 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.

Pull request overview

This PR introduces a new Orleans analyzer + code fix workflow to detect RPC contract drift by comparing grain interfaces in source against a GrainInterfaces.txt manifest, helping prevent accidental incompatibilities during rolling upgrades.

Changes:

  • Added GrainInterfaceVersionAnalyzer (ORLEANS0016–ORLEANS0021) to validate interface declarations, versions, members, file presence, retirements, and duplicates.
  • Added GrainInterfaceVersionCodeFix to update/create GrainInterfaces.txt entries based on diagnostics.
  • Added extensive analyzer/code-fix unit tests and registered new diagnostics in analyzer release notes and resources.
Show a summary per file
File Description
test/Orleans.Analyzers.Tests/GrainInterfaceVersionAnalyzerTest.cs Adds coverage for new diagnostics and code fixes across aliases, versions, generics, and inheritance.
src/Orleans.Analyzers/Resources.resx Adds localized strings for new diagnostics and code fix titles.
src/Orleans.Analyzers/Resources.Designer.cs Updates strongly-typed resource accessors for the new strings.
src/Orleans.Analyzers/GrainInterfaceVersionCodeFix.cs Implements code fixes which update/create GrainInterfaces.txt based on diagnostics.
src/Orleans.Analyzers/GrainInterfaceVersionAnalyzer.cs Implements analyzer + manifest parser for interface/member/version tracking.
src/Orleans.Analyzers/Constants.cs Adds constants for VersionAttribute FQN and GrainInterfaces.txt filename.
src/Orleans.Analyzers/AnalyzerReleases.Unshipped.md Documents the newly introduced diagnostic IDs and severities.

Copilot's findings

Files not reviewed (1)
  • src/Orleans.Analyzers/Resources.Designer.cs: Generated file
Suppressed comments (2)

src/Orleans.Analyzers/GrainInterfaceVersionCodeFix.cs:366

  • AddMemberToFileAsync suppresses insertion when the next interface declaration line StartsWith(interfaceName). This fails when the next interface name happens to share the same prefix (for example, IMyGrain followed by IMyGrainExtended), causing the new member to be inserted in the wrong section.
                // If next line is empty, a comment, or another interface, insert the member here
                if (string.IsNullOrEmpty(nextLine) ||
                    nextLine.StartsWith("#", StringComparison.Ordinal) ||
                    (nextLine.Contains("[Version(") && !nextLine.StartsWith(interfaceName, StringComparison.Ordinal)))
                {

src/Orleans.Analyzers/GrainInterfaceVersionCodeFix.cs:422

  • RetireInterfaceInFileAsync uses trimmedLine.Contains(interfaceName), which can retire the wrong interface when one interface name is a substring/prefix of another. It should ensure it matched the full interface-name token before adding the RETIRED prefix.
            // Check if this line contains the interface declaration
            if (trimmedLine.Contains(interfaceName) && trimmedLine.Contains("[Version(") &&
                !trimmedLine.StartsWith(GrainInterfaceVersionAnalyzer.RetiredPrefix, StringComparison.Ordinal))
            {
  • Files reviewed: 6/7 changed files
  • Comments generated: 4

Comment thread src/Orleans.Analyzers/GrainInterfaceVersionCodeFix.cs Outdated
Comment thread src/Orleans.Analyzers/Resources.resx Outdated
Comment thread src/Orleans.Analyzers/GrainInterfaceVersionAnalyzer.cs
Comment thread src/Orleans.Analyzers/GrainInterfaceVersionCodeFix.cs
Copilot AI review requested due to automatic review settings August 5, 2026 23:47
Recover the grain interface version analyzer and its code fixes. Track grain interface signatures in GrainInterfaces.txt so incompatible changes are identified during development.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 72583b82-973f-4897-939d-e29e6cedd2a0
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 20a2d18d-fb94-43cc-8f92-67274910d221
@ReubenBond
ReubenBond force-pushed the recover-rpc-contract-analyzer branch from df743cf to 2c62824 Compare August 5, 2026 23:49

Copilot AI 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.

Copilot's findings

Files not reviewed (1)
  • src/Orleans.Analyzers/Resources.Designer.cs: Generated file
Suppressed comments (1)

src/Orleans.Analyzers/GrainInterfaceVersionCodeFix.cs:263

  • AddGrainClassToFileAsync only reactivates an existing contracts entry when the declared CLR name matches className. If a grain class is renamed but keeps a stable [GrainType("...")] alias, the analyzer can still match by alias, but the code fix will append a new entry instead of updating the existing (possibly RETIRED) one. That can introduce duplicate alias declarations, which are treated as errors by the parser (see GrainInterfaceFileParser.Parse duplicate check in GrainInterfaceVersionAnalyzer.cs:1003-1005). Consider matching existing entries by alias (and updating/unretiring that line) before appending a new declaration.
        for (var i = 0; i < lines.Length; i++)
        {
            if (GrainInterfaceFileParser.TryGetGrainClassName(lines[i], out var declaredName)
                && string.Equals(declaredName, className, StringComparison.Ordinal))
            {
  • Files reviewed: 23/24 changed files
  • Comments generated: 0 new

Copilot AI review requested due to automatic review settings August 5, 2026 23:53

Copilot AI 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.

Copilot's findings

Files not reviewed (1)
  • src/Orleans.Analyzers/Resources.Designer.cs: Generated file
Suppressed comments (1)

src/Orleans.Runtime/OrleansContracts.txt:31

  • The contract manifest entry for IGrainDirectoryPartition.GetSnapshotAsync drops the nullable annotation from the return type. The source interface returns ValueTask<GrainDirectoryPartitionSnapshot?>, so this signature should include ? to match the analyzer's contract type rendering (it preserves nullable reference modifiers, e.g., GrainAddress?).
  GetSnapshotAsync(Orleans.Runtime.MembershipVersion, Orleans.Runtime.MembershipVersion, RingRange, System.Threading.CancellationToken) -> ValueTask<GrainDirectoryPartitionSnapshot>
  • Files reviewed: 23/24 changed files
  • Comments generated: 0 new

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants