diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/DoNotCopyValue.cs b/src/Roslyn.Diagnostics.Analyzers/Core/DoNotCopyValue.cs index a692760eca..be1cf37fb0 100644 --- a/src/Roslyn.Diagnostics.Analyzers/Core/DoNotCopyValue.cs +++ b/src/Roslyn.Diagnostics.Analyzers/Core/DoNotCopyValue.cs @@ -364,9 +364,14 @@ bool IsSupportedConversion() return false; } - if (Acquire(operation.Operand) == RefKind.None) + switch (Acquire(operation.Operand)) { - return true; + case RefKind.None: + case RefKind.Ref when operation.Conversion.IsIdentity: + return true; + + default: + break; } return false; @@ -540,6 +545,21 @@ public override void VisitForEachLoop(IForEachLoopOperation operation) CheckLocalSymbolInUnsupportedContext(operation, local); } + var instance = operation.Collection; + var instance2 = (operation.Collection as IConversionOperation)?.Operand; + if (Acquire(operation.Collection) != RefKind.Ref) + { + instance = null; + instance2 = null; + } + else if (Acquire(instance2) != RefKind.Ref) + { + instance2 = null; + } + + using var releaser = TryAddForVisit(_handledOperations, instance, out _); + using var releaser2 = TryAddForVisit(_handledOperations, instance2, out _); + CheckTypeInUnsupportedContext(operation); base.VisitForEachLoop(operation); } diff --git a/src/Roslyn.Diagnostics.Analyzers/UnitTests/DoNotCopyValueTests.cs b/src/Roslyn.Diagnostics.Analyzers/UnitTests/DoNotCopyValueTests.cs index 378adb43fa..435c85abc1 100644 --- a/src/Roslyn.Diagnostics.Analyzers/UnitTests/DoNotCopyValueTests.cs +++ b/src/Roslyn.Diagnostics.Analyzers/UnitTests/DoNotCopyValueTests.cs @@ -636,6 +636,125 @@ struct CannotCopy public int Second { get; set; } } +internal sealed class NonCopyableAttribute : System.Attribute { } +"; + + await new VerifyCS.Test + { + TestCode = source, + LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8, + }.RunAsync(); + } + + [Fact] + public async Task AllowCustomForeachEnumerator() + { + var source = @" +using System.Runtime.InteropServices; + +class C +{ + void Method() + { + var cannotCopy = new CannotCopy(); + foreach (var obj in cannotCopy) + { + } + } +} + +[NonCopyable] +struct CannotCopy +{ + public Enumerator GetEnumerator() => throw null; + + public struct Enumerator + { + public object Current => throw null; + public bool MoveNext() => throw null; + } +} + +internal sealed class NonCopyableAttribute : System.Attribute { } +"; + + await new VerifyCS.Test + { + TestCode = source, + LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8, + }.RunAsync(); + } + + [Fact] + public async Task AllowCustomForeachEnumeratorDisposableObject() + { + var source = @" +using System; +using System.Runtime.InteropServices; + +class C +{ + void Method() + { + using var cannotCopy = new CannotCopy(); + foreach (var obj in cannotCopy) + { + } + } +} + +[NonCopyable] +struct CannotCopy : IDisposable +{ + public void Dispose() => throw null; + public Enumerator GetEnumerator() => throw null; + + public struct Enumerator + { + public object Current => throw null; + public bool MoveNext() => throw null; + } +} + +internal sealed class NonCopyableAttribute : System.Attribute { } +"; + + await new VerifyCS.Test + { + TestCode = source, + LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8, + }.RunAsync(); + } + + [Fact] + public async Task AllowCustomForeachReadonlyEnumerator() + { + var source = @" +using System.Runtime.InteropServices; + +class C +{ + void Method() + { + var cannotCopy = new CannotCopy(); + foreach (var obj in cannotCopy) + { + } + } +} + +[NonCopyable] +struct CannotCopy +{ + public readonly Enumerator GetEnumerator() => throw null; + + public struct Enumerator + { + public object Current => throw null; + public bool MoveNext() => throw null; + } +} + internal sealed class NonCopyableAttribute : System.Attribute { } ";