Skip to content

Commit 8584580

Browse files
Do not report CA2007 for pattern-based await using and await foreach (#55036)
2 parents 4a5be3c + 7da5237 commit 8584580

2 files changed

Lines changed: 116 additions & 21 deletions

File tree

  • src/Microsoft.CodeAnalysis.NetAnalyzers

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.CodeQuality.Analyzers/ApiDesignGuidelines/DoNotDirectlyAwaitATask.cs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
54
using System.Collections.Immutable;
6-
using System.Linq;
75
using Analyzer.Utilities;
86
using Analyzer.Utilities.Extensions;
97
using Microsoft.CodeAnalysis;
@@ -54,7 +52,10 @@ public override void Initialize(AnalysisContext context)
5452
return;
5553
}
5654

55+
var iAsyncDisposable = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIAsyncDisposable);
5756
var configuredAsyncDisposable = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesConfiguredAsyncDisposable);
57+
58+
var iAsyncEnumerable = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericIAsyncEnumerable1);
5859
var configuredAsyncEnumerable = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesConfiguredCancelableAsyncEnumerable);
5960

6061
context.RegisterOperationBlockStartAction(context =>
@@ -75,27 +76,33 @@ public override void Initialize(AnalysisContext context)
7576
}
7677

7778
context.RegisterOperationAction(context => AnalyzeAwaitOperation(context, taskTypes), OperationKind.Await);
78-
if (configuredAsyncDisposable is not null)
79+
80+
if (iAsyncDisposable is not null && configuredAsyncDisposable is not null)
7981
{
80-
context.RegisterOperationAction(context => AnalyzeUsingOperation(context, configuredAsyncDisposable), OperationKind.Using);
81-
context.RegisterOperationAction(context => AnalyzeUsingDeclarationOperation(context, configuredAsyncDisposable), OperationKind.UsingDeclaration);
82+
context.RegisterOperationAction(context => AnalyzeUsingOperation(context, iAsyncDisposable, configuredAsyncDisposable), OperationKind.Using);
83+
context.RegisterOperationAction(context => AnalyzeUsingDeclarationOperation(context, iAsyncDisposable, configuredAsyncDisposable), OperationKind.UsingDeclaration);
8284
}
8385

84-
if (configuredAsyncEnumerable is not null)
86+
if (iAsyncEnumerable is not null && configuredAsyncEnumerable is not null)
8587
{
86-
context.RegisterOperationAction(ctx => AnalyzeAwaitForEachLoopOperation(ctx, configuredAsyncEnumerable), OperationKind.Loop);
88+
context.RegisterOperationAction(ctx => AnalyzeAwaitForEachLoopOperation(ctx, iAsyncEnumerable, configuredAsyncEnumerable), OperationKind.Loop);
8789
}
8890
}
8991
});
9092
});
9193
}
9294

93-
private static void AnalyzeAwaitForEachLoopOperation(OperationAnalysisContext context, INamedTypeSymbol configuredAsyncEnumerable)
95+
private static void AnalyzeAwaitForEachLoopOperation(OperationAnalysisContext context, INamedTypeSymbol iAsyncEnumerable, INamedTypeSymbol configuredAsyncEnumerable)
9496
{
95-
if (context.Operation is IForEachLoopOperation { IsAsynchronous: true, Collection.Type: not null } forEachOperation
96-
&& !forEachOperation.Collection.Type.OriginalDefinition.Equals(configuredAsyncEnumerable, SymbolEqualityComparer.Default))
97+
if (context.Operation is IForEachLoopOperation { IsAsynchronous: true, Collection.Type: not null } forEachOperation)
9798
{
98-
context.ReportDiagnostic(forEachOperation.Collection.CreateDiagnostic(Rule));
99+
var collectionTypeOriginalDefinition = forEachOperation.Collection.Type.OriginalDefinition;
100+
101+
if (!collectionTypeOriginalDefinition.Equals(configuredAsyncEnumerable, SymbolEqualityComparer.Default) &&
102+
context.Compilation.ClassifyCommonConversion(collectionTypeOriginalDefinition, iAsyncEnumerable) is { Exists: true, IsImplicit: true })
103+
{
104+
context.ReportDiagnostic(forEachOperation.Collection.CreateDiagnostic(Rule));
105+
}
99106
}
100107
}
101108

@@ -111,7 +118,7 @@ private static void AnalyzeAwaitOperation(OperationAnalysisContext context, Immu
111118
}
112119
}
113120

114-
private static void AnalyzeUsingOperation(OperationAnalysisContext context, INamedTypeSymbol configuredAsyncDisposable)
121+
private static void AnalyzeUsingOperation(OperationAnalysisContext context, INamedTypeSymbol iAsyncDisposable, INamedTypeSymbol configuredAsyncDisposable)
115122
{
116123
var usingExpression = (IUsingOperation)context.Operation;
117124
if (!usingExpression.IsAsynchronous)
@@ -121,12 +128,16 @@ private static void AnalyzeUsingOperation(OperationAnalysisContext context, INam
121128

122129
if (usingExpression.Resources is IVariableDeclarationGroupOperation variableDeclarationGroup)
123130
{
131+
var compilation = context.Compilation;
132+
124133
foreach (var declaration in variableDeclarationGroup.Declarations)
125134
{
126135
foreach (var declarator in declaration.Declarators)
127136
{
128-
// Get the type of the expression being awaited and check it's a task type.
129-
if (declarator.Symbol.Type != configuredAsyncDisposable)
137+
var declaratorSymbolType = declarator.Symbol.Type;
138+
139+
if (!declaratorSymbolType.Equals(configuredAsyncDisposable, SymbolEqualityComparer.Default) &&
140+
compilation.ClassifyCommonConversion(declaratorSymbolType, iAsyncDisposable) is { Exists: true, IsImplicit: true })
130141
{
131142
var reportingOperation = declarator.Initializer?.Value ?? declarator;
132143
context.ReportDiagnostic(reportingOperation.CreateDiagnostic(Rule));
@@ -136,20 +147,24 @@ private static void AnalyzeUsingOperation(OperationAnalysisContext context, INam
136147
}
137148
}
138149

139-
private static void AnalyzeUsingDeclarationOperation(OperationAnalysisContext context, INamedTypeSymbol configuredAsyncDisposable)
150+
private static void AnalyzeUsingDeclarationOperation(OperationAnalysisContext context, INamedTypeSymbol iAsyncDisposable, INamedTypeSymbol configuredAsyncDisposable)
140151
{
141152
var usingExpression = (IUsingDeclarationOperation)context.Operation;
142153
if (!usingExpression.IsAsynchronous)
143154
{
144155
return;
145156
}
146157

158+
var compilation = context.Compilation;
159+
147160
foreach (var declaration in usingExpression.DeclarationGroup.Declarations)
148161
{
149162
foreach (var declarator in declaration.Declarators)
150163
{
151-
// Get the type of the expression being awaited and check it's a task type.
152-
if (declarator.Symbol.Type != configuredAsyncDisposable)
164+
var declaratorSymbolType = declarator.Symbol.Type;
165+
166+
if (!declaratorSymbolType.Equals(configuredAsyncDisposable, SymbolEqualityComparer.Default) &&
167+
compilation.ClassifyCommonConversion(declaratorSymbolType, iAsyncDisposable) is { Exists: true, IsImplicit: true })
153168
{
154169
var reportingOperation = declarator.Initializer?.Value ?? declarator;
155170
context.ReportDiagnostic(reportingOperation.CreateDiagnostic(Rule));

src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.CodeQuality.Analyzers/ApiDesignGuidelines/DoNotDirectlyAwaitATaskTests.cs

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Immutable;
5-
using System.Threading.Tasks;
6-
using Microsoft.CodeAnalysis.Testing;
7-
using Test.Utilities;
85
using Microsoft.CodeAnalysis.CSharp;
6+
using Microsoft.CodeAnalysis.Testing;
97
using VerifyCS = Test.Utilities.CSharpCodeFixVerifier<
108
Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines.DoNotDirectlyAwaitATaskAnalyzer,
119
Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines.DoNotDirectlyAwaitATaskFixer>;
@@ -823,5 +821,87 @@ public void Test(IEnumerable<int> enumerable)
823821
LanguageVersion = LanguageVersion.CSharp8
824822
}.RunAsync(CancellationToken.None);
825823
}
824+
825+
[TestMethod, WorkItem(53461, "https://github.com/dotnet/sdk/issues/53461")]
826+
public Task CSharpNoDiagnosticForPatternBasedAwaitUsing_UsingStatement()
827+
{
828+
return new VerifyCS.Test
829+
{
830+
TestCode = """
831+
using System.Runtime.CompilerServices;
832+
using System.Threading.Tasks;
833+
834+
public struct CustomAsyncDisposable
835+
{
836+
public ConfiguredValueTaskAwaitable DisposeAsync() => default;
837+
}
838+
839+
public static class Class
840+
{
841+
public static async Task Test()
842+
{
843+
await using (var cad = new CustomAsyncDisposable())
844+
{
845+
}
846+
}
847+
}
848+
""",
849+
LanguageVersion = LanguageVersion.CSharp8
850+
}.RunAsync(CancellationToken.None);
851+
}
852+
853+
[TestMethod, WorkItem(53461, "https://github.com/dotnet/sdk/issues/53461")]
854+
public Task CSharpNoDiagnosticForPatternBasedAwaitUsing_UsingDeclaration()
855+
{
856+
return new VerifyCS.Test
857+
{
858+
TestCode = """
859+
using System.Runtime.CompilerServices;
860+
using System.Threading.Tasks;
861+
862+
public struct CustomAsyncDisposable
863+
{
864+
public ConfiguredValueTaskAwaitable DisposeAsync() => default;
865+
}
866+
867+
public static class Class
868+
{
869+
public static async Task Test()
870+
{
871+
await using var cad = new CustomAsyncDisposable();
872+
}
873+
}
874+
""",
875+
LanguageVersion = LanguageVersion.CSharp8
876+
}.RunAsync(CancellationToken.None);
877+
}
878+
879+
[TestMethod, WorkItem(53461, "https://github.com/dotnet/sdk/issues/53461")]
880+
public Task CSharpNoDiagnosticForPatternBasedAwaitForEach()
881+
{
882+
return new VerifyCS.Test
883+
{
884+
TestCode = """
885+
using System.Runtime.CompilerServices;
886+
using System.Threading.Tasks;
887+
888+
public struct CustomAsyncEnumerable<T>
889+
{
890+
public ConfiguredCancelableAsyncEnumerable<T>.Enumerator GetAsyncEnumerator() => default;
891+
}
892+
893+
public static class Class
894+
{
895+
public static async Task Test()
896+
{
897+
await foreach (var i in new CustomAsyncEnumerable<int>())
898+
{
899+
}
900+
}
901+
}
902+
""",
903+
LanguageVersion = LanguageVersion.CSharp8
904+
}.RunAsync(CancellationToken.None);
905+
}
826906
}
827-
}
907+
}

0 commit comments

Comments
 (0)