Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,19 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
}

var memberAccessExpression = blockingIdentifier?.Parent as MemberAccessExpressionSyntax;
var blockingInvocation = memberAccessExpression?.Parent as InvocationExpressionSyntax;
IMethodSymbol? blockingMethod = blockingInvocation is object
? semanticModel.GetSymbolInfo(blockingInvocation, context.CancellationToken).Symbol as IMethodSymbol
: null;

// Check whether this code was already calling the awaiter (in a synchronous fashion).
asyncAlternativeExists |= memberAccessExpression?.Expression is InvocationExpressionSyntax invoke && invoke.Expression is MemberAccessExpressionSyntax parentMemberAccess && parentMemberAccess.Name.Identifier.Text == nameof(Task.GetAwaiter);

if (!asyncAlternativeExists)
if (string.IsNullOrEmpty(diagnostic.Properties[VSTHRD103UseAsyncOptionAnalyzer.AsyncMethodKeyName]) && blockingMethod?.IsStatic is true)
{
asyncAlternativeExists = IsAwaitableTaskWaitAll(blockingMethod);
}
else if (!asyncAlternativeExists)
{
// If we fail to recognize the container, assume it exists since the analyzer thought it would.
ITypeSymbol? container = memberAccessExpression is object ? semanticModel.GetTypeInfo(memberAccessExpression.Expression, context.CancellationToken).ConvertedType : null;
Expand All @@ -87,6 +95,14 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
/// <inheritdoc />
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

private static bool IsAwaitableTaskWaitAll(IMethodSymbol method)
{
return method.Name == nameof(Task.WaitAll)
&& method.ContainingType.Name == nameof(Task)
&& method.ContainingNamespace.ToDisplayString() == typeof(Task).Namespace
&& method.Parameters is [{ Type: IArrayTypeSymbol { ElementType.Name: nameof(Task) } }];
}

private class ReplaceSyncMethodCallWithAwaitAsync : CodeAction
{
private readonly Document document;
Expand Down Expand Up @@ -143,6 +159,10 @@ public override string Title
SemanticModel? semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
AnonymousFunctionExpressionSyntax? originalAnonymousMethodContainerIfApplicable = syncMethodName.FirstAncestorOrSelf<AnonymousFunctionExpressionSyntax>();
MethodDeclarationSyntax originalMethodDeclaration = syncMethodName.FirstAncestorOrSelf<MethodDeclarationSyntax>() ?? throw new InvalidOperationException("Unable to find containing method.");
InvocationExpressionSyntax? syncInvocation = syncMethodName.FirstAncestorOrSelf<InvocationExpressionSyntax>();
bool isAwaitableTaskWaitAll = syncInvocation is object
&& semanticModel?.GetSymbolInfo(syncInvocation, cancellationToken).Symbol is IMethodSymbol methodSymbol
&& IsAwaitableTaskWaitAll(methodSymbol);

ISymbol? enclosingSymbol = semanticModel?.GetEnclosingSymbol(this.diagnostic.Location.SourceSpan.Start, cancellationToken);
var hasReturnValue = ((enclosingSymbol as IMethodSymbol)?.ReturnType as INamedTypeSymbol)?.IsGenericType ?? false;
Expand Down Expand Up @@ -170,7 +190,14 @@ public override string Title
ExpressionSyntax syncExpression = GetSynchronousExpression(syncMethodName) ?? throw new InvalidOperationException("Unable to find sync expression.");

ExpressionSyntax awaitExpression;
if (!string.IsNullOrEmpty(this.AlternativeAsyncMethod))
if (isAwaitableTaskWaitAll)
{
SimpleNameSyntax whenAllMethodName = syncMethodName.WithIdentifier(SyntaxFactory.Identifier(nameof(Task.WhenAll)));
awaitExpression = SyntaxFactory.AwaitExpression(
syncExpression.ReplaceNode(syncMethodName, whenAllMethodName).WithoutLeadingTrivia())
.WithLeadingTrivia(syncExpression.GetLeadingTrivia());
}
else if (!string.IsNullOrEmpty(this.AlternativeAsyncMethod))
{
// Replace the member being called and await the invocation expression.
// While doing so, move leading trivia to the surrounding await expression.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,74 @@ async Task T() {
await CSVerify.VerifyCodeFixAsync(test, expected, withFix);
}

[Fact]
public async Task TaskWaitAllInTaskReturningMethodGeneratesWarning()
{
var test = @"
using System.Threading.Tasks;

class Test {
Task T() {
Task[] tasks = null;
Task.WaitAll(tasks);
Comment thread
AArnott marked this conversation as resolved.
Resolved
return Task.CompletedTask;
}
}
";

var withFix = @"
using System.Threading.Tasks;

class Test {
async Task T() {
Task[] tasks = null;
await Task.WhenAll(tasks);
}
}
";
DiagnosticResult expected = CSVerify.Diagnostic(DescriptorNoAlternativeMethod).WithLocation(7, 14).WithArguments("WaitAll");
await CSVerify.VerifyCodeFixAsync(test, expected, withFix);
}

[Fact]
public async Task TaskWaitAllWithCancellationTokenOffersNoFix()
{
var test = @"
using System.Threading;
using System.Threading.Tasks;

class Test {
Task T() {
Task[] tasks = null;
Task.{|#0:WaitAll|}(tasks, CancellationToken.None);
return Task.CompletedTask;
}
}
";

DiagnosticResult expected = CSVerify.Diagnostic(DescriptorNoAlternativeMethod).WithLocation(0).WithArguments("WaitAll");
await CSVerify.VerifyCodeFixAsync(test, expected, test);
}

[Fact]
public async Task TaskWaitAnyOffersNoFix()
{
var test = @"
using System.Threading.Tasks;

class Test {
Task T() {
Task[] tasks = null;
Task.{|#0:WaitAny|}(tasks);
return Task.CompletedTask;
}
}
";

DiagnosticResult expected = CSVerify.Diagnostic(DescriptorNoAlternativeMethod).WithLocation(0).WithArguments("WaitAny");
await CSVerify.VerifyCodeFixAsync(test, expected, test);
}

[Fact]
public async Task TaskWaitInValueTaskReturningMethodGeneratesWarning()
{
Expand Down
Loading