Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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,101 @@ 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.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(StatementSyntax statement, ExpressionSyntax expression, ISymbol expectedTaskSymbol)
{
foreach (SyntaxNode node in statement.DescendantNodes().Where(node => node.SpanStart < expression.SpanStart))
{
if (node is RefExpressionSyntax refExpression
&& SymbolEqualityComparer.Default.Equals(
context.SemanticModel.GetSymbolInfo(refExpression.Expression, context.CancellationToken).Symbol,
expectedTaskSymbol))
{
return true;
}

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;
}
}
}
}
23 changes: 21 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,27 @@ 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)
{
IEnumerable<ISymbol> receiverMembers = receiverType.TypeKind == TypeKind.Interface
? receiverType.AllInterfaces.SelectMany(iface => iface.GetMembers(asyncMethodName))
: Enumerable.Empty<ISymbol>();
for (INamedTypeSymbol? currentType = receiverType; currentType is not null; currentType = currentType.BaseType)
{
receiverMembers = receiverMembers.Concat(currentType.GetMembers(asyncMethodName));
}

return receiverMembers
.Any(alt => IsXAtLeastAsPublicAsY(alt, methodSymbol));
}

return false;
}

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