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 @@ -98,12 +98,109 @@ private void InspectMemberAccess(SyntaxNodeAnalysisContext context, MemberAccess
{
if (item.Method.IsMatch(invokedMember))
{
if (invokedMember is IPropertySymbol { Name: nameof(Task<int>.Result), ContainingType: { } containingType }
&& Utils.IsTask(containingType)
&& this.IsGuardedBySuccessfulCompletion(context, memberAccessSyntax))
{
return;
}

Location? location = memberAccessSyntax.Name.GetLocation();
context.ReportDiagnostic(Diagnostic.Create(Descriptor, location));
this.diagnosticReported = true;
}
}
}
}

private bool IsGuardedBySuccessfulCompletion(SyntaxNodeAnalysisContext context, MemberAccessExpressionSyntax resultAccess)
{
ISymbol? taskSymbol = context.SemanticModel.GetSymbolInfo(resultAccess.Expression, context.CancellationToken).Symbol;
if (taskSymbol is not ILocalSymbol and not IParameterSymbol)
{
return false;
}
Comment thread
AArnott marked this conversation as resolved.
Resolved

foreach (IfStatementSyntax ifStatement in resultAccess.Ancestors()
.TakeWhile(ancestor => ancestor is not LocalFunctionStatementSyntax)
.OfType<IfStatementSyntax>())
{
if (ifStatement.Statement.Span.Contains(resultAccess.Span)
&& ConditionProvesSuccessfulCompletion(ifStatement.Condition, taskSymbol)
&& !MayWriteSymbolBefore(ifStatement.Condition, resultAccess, taskSymbol)
&& !MayWriteSymbolBefore(ifStatement.Statement, resultAccess, taskSymbol))
{
return true;
}
}

return false;

bool ConditionProvesSuccessfulCompletion(ExpressionSyntax condition, ISymbol expectedTaskSymbol)
{
while (condition is ParenthesizedExpressionSyntax parenthesized)
{
condition = parenthesized.Expression;
}

if (condition is BinaryExpressionSyntax { RawKind: (int)SyntaxKind.LogicalAndExpression } logicalAnd)
{
return ConditionProvesSuccessfulCompletion(logicalAnd.Left, expectedTaskSymbol)
|| ConditionProvesSuccessfulCompletion(logicalAnd.Right, expectedTaskSymbol);
}

if (condition is not MemberAccessExpressionSyntax { Name.Identifier.ValueText: "IsCompletedSuccessfully" } completionAccess)
{
return false;
}
Comment thread
AArnott marked this conversation as resolved.
Resolved

ISymbol? completionProperty = context.SemanticModel.GetSymbolInfo(completionAccess, context.CancellationToken).Symbol;
ISymbol? completedTaskSymbol = context.SemanticModel.GetSymbolInfo(completionAccess.Expression, context.CancellationToken).Symbol;
return completionProperty is IPropertySymbol { ContainingType: { } containingType }
&& Utils.IsTask(containingType)
&& SymbolEqualityComparer.Default.Equals(expectedTaskSymbol, completedTaskSymbol);
}

bool MayWriteSymbolBefore(SyntaxNode region, ExpressionSyntax expression, ISymbol expectedTaskSymbol)
{
SyntaxNode executableScope = expression.Ancestors().FirstOrDefault(ancestor =>
ancestor is AnonymousFunctionExpressionSyntax
or LocalFunctionStatementSyntax
or BaseMethodDeclarationSyntax
or AccessorDeclarationSyntax) ?? region;
if (executableScope.DescendantNodes()
.OfType<RefExpressionSyntax>()
.Where(refExpression => refExpression.SpanStart < expression.SpanStart)
.Any(refExpression => SymbolEqualityComparer.Default.Equals(
context.SemanticModel.GetSymbolInfo(refExpression.Expression, context.CancellationToken).Symbol,
expectedTaskSymbol)))
{
return true;
}

foreach (SyntaxNode node in region.DescendantNodes().Where(node => node.SpanStart < expression.SpanStart))
{
ExpressionSyntax? writtenExpression = node switch
{
AssignmentExpressionSyntax assignment => assignment.Left,
PrefixUnaryExpressionSyntax prefix when prefix.IsKind(SyntaxKind.PreIncrementExpression) || prefix.IsKind(SyntaxKind.PreDecrementExpression) => prefix.Operand,
PostfixUnaryExpressionSyntax postfix when postfix.IsKind(SyntaxKind.PostIncrementExpression) || postfix.IsKind(SyntaxKind.PostDecrementExpression) => postfix.Operand,
ArgumentSyntax argument when argument.RefOrOutKeyword.IsKind(SyntaxKind.RefKeyword) || argument.RefOrOutKeyword.IsKind(SyntaxKind.OutKeyword) => argument.Expression,
_ => null,
};

if (writtenExpression is not null && writtenExpression.DescendantNodesAndSelf()
.OfType<ExpressionSyntax>()
.Any(candidate => SymbolEqualityComparer.Default.Equals(
context.SemanticModel.GetSymbolInfo(candidate, context.CancellationToken).Symbol,
expectedTaskSymbol)))
{
return true;
}
}

return false;
}
}
}
}
28 changes: 26 additions & 2 deletions src/Microsoft.VisualStudio.Threading.Analyzers/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,32 @@ public static bool IsLazyOfT([NotNullWhen(true)] INamedTypeSymbol? constructedTy
public static bool HasAsyncAlternative(this IMethodSymbol methodSymbol, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
return methodSymbol.ContainingType.GetMembers(methodSymbol.Name + VSTHRD200UseAsyncNamingConventionAnalyzer.MandatoryAsyncSuffix)
.Any(alt => IsXAtLeastAsPublicAsY(alt, methodSymbol));
string asyncMethodName = methodSymbol.Name + VSTHRD200UseAsyncNamingConventionAnalyzer.MandatoryAsyncSuffix;
if (methodSymbol.ContainingType.GetMembers(asyncMethodName).Any(alt => IsXAtLeastAsPublicAsY(alt, methodSymbol)))
{
return true;
}

if (methodSymbol.IsExtensionMethod && !methodSymbol.Parameters.IsDefaultOrEmpty && methodSymbol.Parameters[0].Type is INamedTypeSymbol receiverType)
{
if (receiverType.TypeKind == TypeKind.Interface
&& receiverType.AllInterfaces.Any(iface => iface.GetMembers(asyncMethodName).Any(alt => IsXAtLeastAsPublicAsY(alt, methodSymbol))))
{
return true;
}

for (INamedTypeSymbol? currentType = receiverType; currentType is not null; currentType = currentType.BaseType)
{
if (currentType.GetMembers(asyncMethodName).Any(alt => IsXAtLeastAsPublicAsY(alt, methodSymbol)))
{
return true;
}
}

return false;
}

return false;
}

public static bool IsXAtLeastAsPublicAsY(ISymbol x, ISymbol y)
Expand Down
Loading
Loading