Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggest CA2007 for await foreach without specified cancellation #6683

Merged
merged 13 commits into from
Oct 16, 2023

Conversation

CollinAlpert
Copy link
Contributor

This PR extends the DoNotDirectlyAwaitATask analyzer for IAsyncEnumerable. Let me know if there is an easier way to check for an await foreach loop, I found this method to be quite cumbersome.

Fixes #6652.

@CollinAlpert CollinAlpert requested a review from a team as a code owner June 11, 2023 20:00
Comment on lines 94 to 97
public virtual (Action<OperationAnalysisContext> Analysis, OperationKind OperationKind) AnalyzeAwaitForEachLoopOperation(INamedTypeSymbol configuredAsyncEnumerable)
{
return (_ => { }, OperationKind.None);
}
Copy link
Member

@Youssef1313 Youssef1313 Jun 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to make this method return void and do the actual registeration.

protected virtual void RegisterLanguageSpecificChecks(OperationBlockStartAnalysisContext context, INamedTypeSymbol configuredAsyncEnumerable)
{
}

and in C# implementation:

protected override void RegisterLanguageSpecificChecks(OperationBlockStartAnalysisContext context, INamedTypeSymbol configuredAsyncEnumerable)
{
    context.RegisterOperationAction(context => AnalyzeAwaitForEachLoopOperation(context, configuredAsyncEnumerable), OperationKind.Loop);
}

Note: I'm writing directly in GitHub, so I could have made an error.

[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class DoNotDirectlyAwaitATaskAnalyzer : DiagnosticAnalyzer
[DiagnosticAnalyzer(LanguageNames.VisualBasic)]
public class DoNotDirectlyAwaitATaskAnalyzer : DiagnosticAnalyzer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make this abstract, and have VB-derived class.


private static void AnalyzeAwaitForEachLoopOperation(OperationAnalysisContext context, INamedTypeSymbol configuredAsyncEnumerable)
{
if (context.Operation is IForEachLoopOperation {Syntax: ForEachStatementSyntax {AwaitKeyword.RawKind: not (int)SyntaxKind.None}} forEachOperation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should upgrade to newer version of Microsoft.CodeAnalysis package reference for the NetAnalyzers project. We don't really need to support this analyzer package on very old versions of VS/compiler. I'll create a separate PR for this, and remove any old lightup code that was added to work around this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if (context.Operation is IForEachLoopOperation {Syntax: ForEachStatementSyntax {AwaitKeyword.RawKind: not (int)SyntaxKind.None}} forEachOperation
&& !forEachOperation.Collection.Type.OriginalDefinition.Equals(configuredAsyncEnumerable, SymbolEqualityComparer.Default))
{
context.ReportDiagnostic(forEachOperation.Collection.Syntax.CreateDiagnostic(Rule));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
context.ReportDiagnostic(forEachOperation.Collection.Syntax.CreateDiagnostic(Rule));
context.ReportDiagnostic(forEachOperation.Collection.CreateDiagnostic(Rule));

@@ -52,7 +53,9 @@ public override void Initialize(AnalysisContext context)
return;
}

var configuredAsyncDisposable = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesConfiguredAsyncDisposable);
var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a good idea to move this variable before TryGetTaskTypes, and pass it instead of passing context.Compilation.

@sharwell
Copy link
Member

My preference is to take earlier pull request #5377 instead of this pull request, provided it remains viable

@mavasani
Copy link
Member

My preference is to take earlier pull request #5377 instead of this pull request, provided it remains viable

@sharwell Are you able to review #5377 and take it forward?

# Conflicts:
#	src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif
#	src/Utilities/Compiler/WellKnownTypeNames.cs
@codecov
Copy link

codecov bot commented Jul 31, 2023

Codecov Report

Merging #6683 (ab446be) into main (ac6c703) will decrease coverage by 0.01%.
The diff coverage is 98.90%.

@@            Coverage Diff             @@
##             main    #6683      +/-   ##
==========================================
- Coverage   96.43%   96.42%   -0.01%     
==========================================
  Files        1410     1410              
  Lines      336154   336238      +84     
  Branches    11103    11106       +3     
==========================================
+ Hits       324158   324230      +72     
- Misses       9200     9212      +12     
  Partials     2796     2796              

# Conflicts:
#	src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif
Comment on lines 14 to 26
protected override void RegisterLanguageSpecificChecks(OperationBlockStartAnalysisContext context, INamedTypeSymbol configuredAsyncEnumerable)
{
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));
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CollinAlpert all the code here seems to be compilable at the shared layer. I understand only C# supports async foreach loops, but even having this code in the shared layer will do the right thing by bailing out early for VB. I would move this code down to the shared layer and we can definitely conclude that this PR is preferable over #5377

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@mavasani mavasani merged commit b87a634 into dotnet:main Oct 16, 2023
11 checks passed
@mavasani
Copy link
Member

Thanks!

@CollinAlpert CollinAlpert deleted the issue_6652 branch October 17, 2023 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Set ConfigureAwait(bool) on IAsyncEnumerators in await foreach loops
4 participants