From 1b5d17b33b29fe4bea00ef1594099c1401d2e9a4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 17 Apr 2025 13:18:06 -0700 Subject: [PATCH] Fix issue where we were innapropriately suggesting we remove a ref-field --- .../RemoveUnusedMembersTests.cs | 29 +++++++++++++++++++ ...ctRemoveUnusedMembersDiagnosticAnalyzer.cs | 12 ++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/RemoveUnusedMembers/RemoveUnusedMembersTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnusedMembers/RemoveUnusedMembersTests.cs index 89e40bcd72362..2fd814622f87c 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnusedMembers/RemoveUnusedMembersTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnusedMembers/RemoveUnusedMembersTests.cs @@ -3530,4 +3530,33 @@ public unsafe void ToUnmanaged() ReferenceAssemblies = ReferenceAssemblies.Net.Net90, }.RunAsync(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77251")] + public async Task TestRefFieldWrittenNotRead() + { + await new VerifyCS.Test + { + TestCode = """ + public readonly ref struct RefScope + { + public RefScope(ref T originalvalue, T newvalue) + { + _OriginalValue = originalvalue; + _Reference = ref originalvalue; + originalvalue = newvalue; + } + + readonly ref T _Reference; // Should get no diagnostic here. + readonly T _OriginalValue; + + public void Dispose() + { + _Reference = _OriginalValue; + } + } + """, + LanguageVersion = LanguageVersion.CSharp13, + ReferenceAssemblies = ReferenceAssemblies.Net.Net90, + }.RunAsync(); + } } diff --git a/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs index fca739ec0c86b..678ebd802def6 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs @@ -583,8 +583,16 @@ private void OnSymbolEnd(SymbolAnalysisContext symbolEndContext, bool hasUnsuppo continue; // Do not flag ref-fields that are not read. A ref-field can exist to have side effects by - // writing into some other location when a write happens to it. - if (member is IFieldSymbol { IsReadOnly: false, RefKind: RefKind.Ref }) + // writing into some other location when a write happens to it. Note: this includes `readonly + // ref` fields as well. It's still legal to assign a normal value into a `readonly ref` field. + // It's just not allowed to overwrite it *with another ref*. In other words: + // + // _readonlyRefField = value; // is fine. + // _readonlyRefField = ref value; // is not. + // + // So as long as it is a ref-field, we don't care if it is unread, but is written to. We must + // continue allowing it. + if (member is IFieldSymbol { RefKind: RefKind.Ref }) continue; }