Skip to content

Commit

Permalink
Merge pull request #6683 from CollinAlpert/issue_6652
Browse files Browse the repository at this point in the history
Suggest CA2007 for await foreach without specified cancellation
  • Loading branch information
mavasani authored Oct 16, 2023
2 parents ac6c703 + ab446be commit b87a634
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Immutable;
using System.Linq;
using Analyzer.Utilities;
Expand Down Expand Up @@ -46,12 +47,14 @@ public override void Initialize(AnalysisContext context)
return;
}

if (!TryGetTaskTypes(context.Compilation, out ImmutableArray<INamedTypeSymbol> taskTypes))
var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation);
if (!TryGetTaskTypes(wellKnownTypeProvider, out ImmutableArray<INamedTypeSymbol> taskTypes))
{
return;
}

var configuredAsyncDisposable = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesConfiguredAsyncDisposable);
var configuredAsyncDisposable = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesConfiguredAsyncDisposable);
var configuredAsyncEnumerable = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesConfiguredCancelableAsyncEnumerable);

context.RegisterOperationBlockStartAction(context =>
{
Expand All @@ -76,11 +79,25 @@ public override void Initialize(AnalysisContext context)
context.RegisterOperationAction(context => AnalyzeUsingOperation(context, configuredAsyncDisposable), OperationKind.Using);
context.RegisterOperationAction(context => AnalyzeUsingDeclarationOperation(context, configuredAsyncDisposable), OperationKind.UsingDeclaration);
}

if (configuredAsyncEnumerable is not null)
{
context.RegisterOperationAction(ctx => AnalyzeAwaitForEachLoopOperation(ctx, configuredAsyncEnumerable), OperationKind.Loop);
}
}
});
});
}

private static void AnalyzeAwaitForEachLoopOperation(OperationAnalysisContext context, INamedTypeSymbol configuredAsyncEnumerable)
{
if (context.Operation is IForEachLoopOperation { IsAsynchronous: true, Collection.Type: not null } forEachOperation
&& !forEachOperation.Collection.Type.OriginalDefinition.Equals(configuredAsyncEnumerable, SymbolEqualityComparer.Default))
{
context.ReportDiagnostic(forEachOperation.Collection.CreateDiagnostic(Rule));
}
}

private static void AnalyzeAwaitOperation(OperationAnalysisContext context, ImmutableArray<INamedTypeSymbol> taskTypes)
{
var awaitExpression = (IAwaitOperation)context.Operation;
Expand Down Expand Up @@ -140,19 +157,19 @@ private static void AnalyzeUsingDeclarationOperation(OperationAnalysisContext co
}
}

private static bool TryGetTaskTypes(Compilation compilation, out ImmutableArray<INamedTypeSymbol> taskTypes)
private static bool TryGetTaskTypes(WellKnownTypeProvider typeProvider, out ImmutableArray<INamedTypeSymbol> taskTypes)
{
INamedTypeSymbol? taskType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask);
INamedTypeSymbol? taskOfTType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask1);
INamedTypeSymbol? taskType = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask);
INamedTypeSymbol? taskOfTType = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask1);

if (taskType == null || taskOfTType == null)
{
taskTypes = ImmutableArray<INamedTypeSymbol>.Empty;
return false;
}

INamedTypeSymbol? valueTaskType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksValueTask);
INamedTypeSymbol? valueTaskOfTType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksValueTask1);
INamedTypeSymbol? valueTaskType = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksValueTask);
INamedTypeSymbol? valueTaskOfTType = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksValueTask1);

taskTypes = valueTaskType != null && valueTaskOfTType != null ?
ImmutableArray.Create(taskType, taskOfTType, valueTaskType, valueTaskOfTType) :
Expand Down
1 change: 0 additions & 1 deletion src/NetAnalyzers/RulesMissingDocumentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ CA1510 | <https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-
CA1511 | <https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1511> | Use ArgumentException throw helper |
CA1512 | <https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1512> | Use ArgumentOutOfRangeException throw helper |
CA1513 | <https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1513> | Use ObjectDisposedException throw helper |
CA1514 | <https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1514> | Avoid redundant length argument |
CA1515 | <https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515> | Consider making public types internal |
CA1856 | <https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1856> | Incorrect usage of ConstantExpected attribute |
CA1857 | <https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1857> | A constant is expected for the parameter |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Microsoft.CodeAnalysis.Testing;
using Test.Utilities;
using Xunit;
using CSharpLanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion;
using Microsoft.CodeAnalysis.CSharp;
using VerifyCS = Test.Utilities.CSharpCodeFixVerifier<
Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines.DoNotDirectlyAwaitATaskAnalyzer,
Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines.DoNotDirectlyAwaitATaskFixer>;
Expand Down Expand Up @@ -174,7 +174,7 @@ public async Task M4()
{
ReferenceAssemblies = ReferenceAssemblies.Default.AddPackages(
ImmutableArray.Create(new PackageIdentity("Microsoft.Bcl.AsyncInterfaces", "5.0.0"))),
LanguageVersion = CSharpLanguageVersion.CSharp8,
LanguageVersion = LanguageVersion.CSharp8,
TestCode = code,
FixedCode = fixedCode,
}.RunAsync();
Expand Down Expand Up @@ -741,5 +741,86 @@ async Task CoreAsync()

await VerifyCS.VerifyCodeFixAsync(code, fixedCode);
}

[Fact, WorkItem(6652, "https://github.com/dotnet/roslyn-analyzers/issues/6652")]
public Task CsharpAwaitIAsyncEnumerable_DiagnosticAsync()
{
return new VerifyCS.Test
{
TestCode = @"
using System.Collections.Generic;
using System.Threading.Tasks;
public class C
{
public async Task Test(IAsyncEnumerable<int> enumerable)
{
await foreach(var i in [|enumerable|])
{
}
}
}",
FixedCode = @"
using System.Collections.Generic;
using System.Threading.Tasks;
public class C
{
public async Task Test(IAsyncEnumerable<int> enumerable)
{
await foreach(var i in enumerable.ConfigureAwait(false))
{
}
}
}",
LanguageVersion = LanguageVersion.CSharp8
}.RunAsync();
}

[Theory, WorkItem(6652, "https://github.com/dotnet/roslyn-analyzers/issues/6652")]
[InlineData("true")]
[InlineData("false")]
public Task CsharpAwaitIAsyncEnumerable_NoDiagnosticAsync(string continueOnCapturedContext)
{
return new VerifyCS.Test
{
TestCode = @$"
using System.Collections.Generic;
using System.Threading.Tasks;
public class C
{{
public async Task Test(IAsyncEnumerable<int> enumerable)
{{
await foreach(var i in enumerable.ConfigureAwait({continueOnCapturedContext}))
{{
}}
}}
}}",
LanguageVersion = LanguageVersion.CSharp8
}.RunAsync();
}

[Fact, WorkItem(6652, "https://github.com/dotnet/roslyn-analyzers/issues/6652")]
public Task CsharpForEachEnumerable_NoDiagnosticAsync()
{
return new VerifyCS.Test
{
TestCode = @"
using System.Collections.Generic;
using System.Threading.Tasks;
public class C
{
public void Test(IEnumerable<int> enumerable)
{
foreach(var i in enumerable)
{
}
}
}",
LanguageVersion = LanguageVersion.CSharp8
}.RunAsync();
}
}
}
1 change: 1 addition & 0 deletions src/Utilities/Compiler/WellKnownTypeNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ internal static class WellKnownTypeNames
public const string SystemRuntimeCompilerServicesCallerArgumentExpressionAttribute = "System.Runtime.CompilerServices.CallerArgumentExpressionAttribute";
public const string SystemRuntimeCompilerServicesCompilerGeneratedAttribute = "System.Runtime.CompilerServices.CompilerGeneratedAttribute";
public const string SystemRuntimeCompilerServicesConfiguredAsyncDisposable = "System.Runtime.CompilerServices.ConfiguredAsyncDisposable";
public const string SystemRuntimeCompilerServicesConfiguredCancelableAsyncEnumerable = "System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1";
public const string SystemRuntimeCompilerServicesConfiguredValueTaskAwaitable = "System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable";
public const string SystemRuntimeCompilerServicesConfiguredValueTaskAwaitable1 = "System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable`1";
public const string SystemRuntimeCompilerServicesDisableRuntimeMarshallingAttribute = "System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute";
Expand Down

0 comments on commit b87a634

Please sign in to comment.