Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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,24 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
}

var memberAccessExpression = blockingIdentifier?.Parent as MemberAccessExpressionSyntax;
InvocationExpressionSyntax? blockingInvocation = blockingIdentifier?.Parent switch
{
InvocationExpressionSyntax invocation => invocation,
MemberAccessExpressionSyntax { Parent: InvocationExpressionSyntax invocation } => invocation,
_ => null,
};
IMethodSymbol? blockingMethod = semanticModel is object && 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]) && semanticModel is object && blockingMethod?.IsStatic is true)
{
asyncAlternativeExists = IsAwaitableTaskWaitAll(blockingMethod, semanticModel.Compilation);
}
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 +100,15 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
/// <inheritdoc />
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

private static bool IsAwaitableTaskWaitAll(IMethodSymbol method, Compilation compilation)
{
INamedTypeSymbol? taskType = compilation.GetTypeByMetadataName(typeof(Task).FullName!);
return method.Name == nameof(Task.WaitAll)
&& SymbolEqualityComparer.Default.Equals(method.ContainingType, taskType)
&& method.Parameters is [{ Type: IArrayTypeSymbol taskArray }]
&& SymbolEqualityComparer.Default.Equals(taskArray.ElementType, taskType);
}

private class ReplaceSyncMethodCallWithAwaitAsync : CodeAction
{
private readonly Document document;
Expand Down Expand Up @@ -143,6 +165,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, semanticModel.Compilation);

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 +196,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,99 @@ 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 TaskWaitAllWithTimeoutOffersNoFix()
{
var test = @"
using System;
using System.Threading.Tasks;

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

DiagnosticResult[] expected =
[
CSVerify.Diagnostic(DescriptorNoAlternativeMethod).WithLocation(0).WithArguments("WaitAll"),
CSVerify.Diagnostic(DescriptorNoAlternativeMethod).WithLocation(1).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