diff --git a/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/ConvertToRecordEngine.cs b/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/ConvertToRecordEngine.cs index 452531f32dac9..7d5bd0dfa5040 100644 --- a/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/ConvertToRecordEngine.cs +++ b/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/ConvertToRecordEngine.cs @@ -218,24 +218,28 @@ await RefactorInitializersAsync(type, solutionEditor, propertiesToAssign, cancel foreach (var method in typeDeclaration.Members.OfType()) { - var methodSymbol = (IMethodSymbol)semanticModel.GetRequiredDeclaredSymbol(method, cancellationToken); - var operation = (IMethodBodyOperation)semanticModel.GetRequiredOperation(method, cancellationToken); + var methodSymbol = semanticModel.GetRequiredDeclaredSymbol(method, cancellationToken); if (methodSymbol.Name == "Clone") { // remove clone method as clone is a reserved method name in records documentEditor.RemoveNode(method); } - else if (ConvertToRecordHelpers.IsSimpleHashCodeMethod( - semanticModel.Compilation, methodSymbol, operation, expectedFields)) + else if (method is { Body: not null }) { - documentEditor.RemoveNode(method); - } - else if (ConvertToRecordHelpers.IsSimpleEqualsMethod( - semanticModel.Compilation, methodSymbol, operation, expectedFields)) - { - // the Equals method implementation is fundamentally equivalent to the generated one - documentEditor.RemoveNode(method); + var operation = (IMethodBodyOperation)semanticModel.GetRequiredOperation(method, cancellationToken); + + if (ConvertToRecordHelpers.IsSimpleHashCodeMethod( + semanticModel.Compilation, methodSymbol, operation, expectedFields)) + { + documentEditor.RemoveNode(method); + } + else if (ConvertToRecordHelpers.IsSimpleEqualsMethod( + semanticModel.Compilation, methodSymbol, operation, expectedFields)) + { + // the Equals method implementation is fundamentally equivalent to the generated one + documentEditor.RemoveNode(method); + } } } diff --git a/src/Features/CSharpTest/ConvertToRecord/ConvertToRecordCodeRefactoringTests.cs b/src/Features/CSharpTest/ConvertToRecord/ConvertToRecordCodeRefactoringTests.cs index dc0b86e7e8fcf..f43705061298e 100644 --- a/src/Features/CSharpTest/ConvertToRecord/ConvertToRecordCodeRefactoringTests.cs +++ b/src/Features/CSharpTest/ConvertToRecord/ConvertToRecordCodeRefactoringTests.cs @@ -4515,6 +4515,55 @@ public record C(int P, bool B); }.RunAsync(); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78664")] + public async Task TestDoesNotCrashOnAbstractMethod() + { + var initialMarkup = """ + namespace N + { + public abstract class [|C|] + { + public string? S { get; set; } + + public abstract System.Guid F(); + } + } + """; + var changedMarkup = """ + namespace N + { + public abstract record C(string? S) + { + public abstract System.Guid F(); + } + } + """; + await TestRefactoringAsync(initialMarkup, changedMarkup); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78664")] + public async Task TestDoesNotCrashOnAbstractCloneMethod() + { + var initialMarkup = """ + namespace N + { + public abstract class [|C|] + { + public string? S { get; set; } + + public abstract object Clone(); + } + } + """; + var changedMarkup = """ + namespace N + { + public abstract record C(string? S); + } + """; + await TestRefactoringAsync(initialMarkup, changedMarkup); + } + #pragma warning disable RS1042 // Do not implement private sealed class ConvertToRecordTestGenerator : ISourceGenerator #pragma warning restore RS1042 // Do not implement