private AnalyzationCandidateResult AnalyzeAsyncCandidates(BodyFunctionDataReference functionReferenceData, IEnumerable <IMethodSymbol> asyncCandidates, bool preferCancellationToken)
        {
            var orderedCandidates = preferCancellationToken
                                ? asyncCandidates.OrderBy(o => o, MethodCancellationTokenComparer.Instance).ToList()
                                : asyncCandidates.OrderByDescending(o => o, MethodCancellationTokenComparer.Instance).ToList();

            if (orderedCandidates.Count == 0)
            {
                return(new AnalyzationCandidateResult
                {
                    AsyncCandidate = null,
                    CanBeAsync = false,
                    IgnoreBodyFunctionDataReferenceReason = IgnoreReason.NoAsyncCounterparts
                });
            }

            // More than one
            // By default we will get here when there are multiple overloads of an async function (e.g. Task.Run<T>(Func<T>, CancellationToken) and Task.Run<T>(Func<Task<T>>, CancellationToken))
            // In the Task.Run case we have to check the delegate argument if it can be async or not (the delegate argument will be processed before the invocation)
            //if (functionReferenceData.DelegateArguments == null)
            //{
            //	return new AnalyzationCandidateResult
            //	{
            //		AsyncCandidate = null,
            //		CanBeAsync = false,
            //		IgnoreBodyFunctionDataReferenceReason = IgnoreReason.Custom("Multiple async counterparts without delegate arguments.", DiagnosticSeverity.Info)
            //	};
            //}

            var validCandidates = new List <AnalyzationCandidateResult>();

            foreach (var asyncCandidate in orderedCandidates)
            {
                var result = AnalyzeAsyncCandidate(functionReferenceData, asyncCandidate, preferCancellationToken);
                if (result.AsyncCandidate != null)
                {
                    validCandidates.Add(result);
                }
            }

            if (validCandidates.Count == 0)
            {
                return(new AnalyzationCandidateResult
                {
                    AsyncCandidate = null,
                    CanBeAsync = false,
                    IgnoreBodyFunctionDataReferenceReason = IgnoreReason.Custom("No async counterparts matches delegate arguments.", DiagnosticSeverity.Info)
                });
            }

            return(validCandidates[0]);
        }
예제 #2
0
 private IMethodSymbol FindAsyncCandidate(BodyFunctionDataReference functionReferenceData, IList <IMethodSymbol> asyncCandidates)
 {
     if (asyncCandidates.Count == 0)
     {
         return(null);
     }
     if (asyncCandidates.Count == 1)
     {
         return(asyncCandidates[0]);
     }
     // More than one
     // By default we will get here when there are multiple overloads of an async function (e.g. Task.Run<T>(Func<T>, CancellationToken) and Task.Run<T>(Func<Task<T>>, CancellationToken))
     // In the Task.Run case we have to check the delegate argument if it can be asnyc or not (the delegate argument will be processed before the invocation)
     if (!functionReferenceData.DelegateArguments.Any())
     {
         functionReferenceData.Ignore(IgnoreReason.Custom("Multiple async counterparts without delegate arguments.", DiagnosticSeverity.Info));
         return(null);
     }
     foreach (var functionArgument in functionReferenceData.DelegateArguments)
     {
         var funcData = functionArgument.FunctionData;
         if (funcData != null)                 // Anonymous function as argument
         {
             if (funcData.BodyFunctionReferences.All(o => o.GetConversion() != ReferenceConversion.ToAsync))
             {
                 return(null);
             }
             CalculatePreserveReturnType(funcData);
             var validOverloads = new List <IMethodSymbol>();
             foreach (var tokenOverload in asyncCandidates)
             {
                 // Check if the return type of the delegate parameter matches with the calculated return type of the anonymous function
                 var delegateSymbol = (IMethodSymbol)tokenOverload.Parameters[functionArgument.Index].Type.GetMembers("Invoke").First();
                 if (
                     (delegateSymbol.ReturnType.IsTaskType() && !funcData.PreserveReturnType) ||
                     (!delegateSymbol.ReturnType.IsTaskType() && funcData.PreserveReturnType && !funcData.Symbol.ReturnType.IsTaskType())
                     )
                 {
                     validOverloads.Add(tokenOverload);
                 }
             }
             asyncCandidates = validOverloads;
         }
         else
         {
             // TODO
             return(null);
         }
     }
     return(asyncCandidates[0]);
 }
        private bool IgnoreIfInvalidAncestor(SyntaxNode node, SyntaxNode endNode, BodyFunctionDataReference functionReferenceData)
        {
            var currAncestor = node.Parent;

            while (!currAncestor.Equals(endNode))
            {
                if (currAncestor.IsKind(SyntaxKind.QueryExpression))
                {
                    functionReferenceData.Ignore(IgnoreReason.Custom("Cannot await async method in a query expression", DiagnosticSeverity.Info));
                    return(true);
                }
                currAncestor = currAncestor.Parent;
            }
            return(false);
        }
        private AnalyzationCandidateResult AnalyzeAsyncCandidate(BodyFunctionDataReference functionReferenceData,
                                                                 IMethodSymbol asyncCandidate, bool useCancellationToken)
        {
            var canBeAsync           = true;
            var asnycDelegateIndexes = functionReferenceData.ReferenceSymbol.GetAsyncDelegateArgumentIndexes(asyncCandidate);

            if (asnycDelegateIndexes != null)
            {
                if (asnycDelegateIndexes.Count == 0 && functionReferenceData.DelegateArguments != null)
                {
                    return(new AnalyzationCandidateResult
                    {
                        AsyncCandidate = asyncCandidate,
                        CanBeAsync = true,
                        IgnoreDelegateArgumentsReason =
                            IgnoreReason.Custom("Argument is not async.", DiagnosticSeverity.Hidden)
                    });
                }
                if (asnycDelegateIndexes.Count > 0 && functionReferenceData.DelegateArguments == null)
                {
                    return(new AnalyzationCandidateResult
                    {
                        AsyncCandidate = null,
                        CanBeAsync = false,
                        IgnoreBodyFunctionDataReferenceReason =
                            IgnoreReason.Custom("Delegate argument is not async.", DiagnosticSeverity.Hidden)
                    });
                }
            }

            if (functionReferenceData.DelegateArguments == null)
            {
                return(new AnalyzationCandidateResult
                {
                    AsyncCandidate = asyncCandidate,
                    CanBeAsync = true
                });
            }

            if (asnycDelegateIndexes != null)
            {
                var delegateIndexes = functionReferenceData.DelegateArguments.Select(o => o.Index).ToList();
                if (delegateIndexes.Count != asnycDelegateIndexes.Count ||
                    asnycDelegateIndexes.Any(o => !delegateIndexes.Contains(o)))
                {
                    return(new AnalyzationCandidateResult
                    {
                        AsyncCandidate = null,
                        CanBeAsync = false,
                        IgnoreBodyFunctionDataReferenceReason =
                            IgnoreReason.Custom("Delegate arguments do not match with the async counterpart.", DiagnosticSeverity.Hidden)
                    });
                }
            }

            foreach (var functionArgument in functionReferenceData.DelegateArguments)
            {
                var funcData = functionArgument.FunctionData;

                if (funcData == null)
                {
                    var bodyRef = functionArgument.FunctionReference;
                    funcData = bodyRef.ReferenceFunctionData;

                    //if (!result.CanBeAsync)
                    //{
                    //	return new AnalyzationCandidateResult
                    //	{
                    //		AsyncCandidate = null,
                    //		CanBeAsync = false,
                    //		IgnoreBodyFunctionDataReferenceReason =
                    //			IgnoreReason.Custom("Delegate argument cannot be async.", DiagnosticSeverity.Hidden)
                    //	};
                    //}

                    if (funcData == null)
                    {
                        var result = AnalyzeAsyncCandidates(bodyRef, bodyRef.ReferenceAsyncSymbols.ToList(), useCancellationToken);
                        if (result.AsyncCandidate != null && functionArgument.Index < asyncCandidate.Parameters.Length)
                        {
                            var delegateSymbol = (IMethodSymbol)asyncCandidate.Parameters[functionArgument.Index].Type.GetMembers("Invoke").First();
                            if (!delegateSymbol.MatchesDefinition(result.AsyncCandidate, true))
                            {
                                return(new AnalyzationCandidateResult
                                {
                                    AsyncCandidate = null,
                                    CanBeAsync = false,
                                    IgnoreBodyFunctionDataReferenceReason =
                                        IgnoreReason.Custom("Delegate argument async counterpart does not match.", DiagnosticSeverity.Hidden)
                                });
                            }
                        }

                        continue;
                    }
                }
                if (funcData.BodyFunctionReferences.All(o => o.GetConversion() == ReferenceConversion.Ignore))
                {
                    return(new AnalyzationCandidateResult
                    {
                        AsyncCandidate = null,
                        CanBeAsync = false,
                        IgnoreBodyFunctionDataReferenceReason =
                            IgnoreReason.Custom("The delegate argument does not have any async invocation.", DiagnosticSeverity.Hidden)
                    });
                }

                canBeAsync &= funcData.BodyFunctionReferences.Any(o => o.GetConversion() == ReferenceConversion.ToAsync);
                //if (funcData.Symbol.MethodKind != MethodKind.AnonymousFunction)
                //{
                //	CalculatePreserveReturnType(funcData);
                //}

                //// Check if the return type of the delegate parameter matches with the calculated return type of the anonymous function
                //if (
                //	(delegateSymbol.ReturnType.SupportsTaskType() && !funcData.PreserveReturnType) ||
                //	(!delegateSymbol.ReturnType.SupportsTaskType() && funcData.PreserveReturnType && !funcData.Symbol.ReturnType.SupportsTaskType())
                //)
                //{
                //	continue;
                //}

                //return new AnalyzationCandidateResult
                //{
                //	AsyncCandidate = null,
                //	CanBeAsync = false,
                //	IgnoreBodyFunctionDataReferenceReason =
                //		IgnoreReason.Custom("Return type of the delegate argument does not match.", DiagnosticSeverity.Hidden)
                //};
            }

            return(new AnalyzationCandidateResult
            {
                AsyncCandidate = asyncCandidate,
                CanBeAsync = canBeAsync
            });
        }
        private void AnalyzeInvocationExpression(DocumentData documentData, InvocationExpressionSyntax node, BodyFunctionDataReference functionReferenceData)
        {
            var functionData = functionReferenceData.Data;
            var methodSymbol = functionReferenceData.ReferenceSymbol;
            var functionNode = functionData.GetNode();

            if (IgnoreIfInvalidAncestor(node, functionNode, functionReferenceData))
            {
                return;
            }

            // If the invocation returns a Task then we need to analyze it further to see how the Task is handled
            if (methodSymbol.ReturnType.IsTaskType())
            {
                var retrunType   = (INamedTypeSymbol)methodSymbol.ReturnType;
                var canBeAwaited = false;
                var currNode     = node.Parent;
                while (true)
                {
                    var memberExpression = currNode as MemberAccessExpressionSyntax;
                    if (memberExpression == null)
                    {
                        break;
                    }
                    var memberName = memberExpression.Name.ToString();
                    if (retrunType.IsGenericType && memberName == "Result")
                    {
                        canBeAwaited = true;
                        break;
                    }
                    if (memberName == "ConfigureAwait")
                    {
                        var invocationNode = currNode.Parent as InvocationExpressionSyntax;
                        if (invocationNode != null)
                        {
                            functionReferenceData.ConfigureAwaitParameter = invocationNode.ArgumentList.Arguments.First().Expression;
                            currNode = invocationNode.Parent;
                            continue;
                        }
                        break;
                    }
                    if (memberName == "GetAwaiter")
                    {
                        var invocationNode = currNode.Parent as InvocationExpressionSyntax;
                        if (invocationNode != null)
                        {
                            currNode = invocationNode.Parent;
                            continue;
                        }
                        break;
                    }
                    if (_taskResultMethods.Contains(memberName))
                    {
                        var invocationNode = currNode.Parent as InvocationExpressionSyntax;
                        if (invocationNode != null)
                        {
                            canBeAwaited = true;
                        }
                    }
                    break;
                }
                if (!canBeAwaited)
                {
                    functionReferenceData.AwaitInvocation = false;
                    functionReferenceData.AddDiagnostic("Cannot await invocation that returns a Task without being synchronously awaited", DiagnosticSeverity.Info);
                }
                else
                {
                    functionReferenceData.SynchronouslyAwaited = true;
                }
            }
            if (node.Expression is SimpleNameSyntax)
            {
                functionReferenceData.InvokedFromType = functionData.Symbol.ContainingType;
            }
            else if (node.Expression is MemberAccessExpressionSyntax memberAccessExpression)
            {
                functionReferenceData.InvokedFromType = documentData.SemanticModel.GetTypeInfo(memberAccessExpression.Expression).Type;
            }

            FindAsyncCounterparts(functionReferenceData);

            var delegateParams = methodSymbol.Parameters.Select(o => o.Type.TypeKind == TypeKind.Delegate).ToList();

            for (var i = 0; i < node.ArgumentList.Arguments.Count; i++)
            {
                var argument           = node.ArgumentList.Arguments[i];
                var argumentExpression = argument.Expression;
                // We have to process anonymous funcions as they will not be analyzed as arguments
                if (argumentExpression.IsFunction())
                {
                    var anonFunction = (AnonymousFunctionData)functionData.ChildFunctions[argumentExpression];
                    functionReferenceData.AddDelegateArgument(new DelegateArgumentData(anonFunction, i));
                    anonFunction.ArgumentOfFunctionInvocation = functionReferenceData;
                    continue;
                }
                if (argumentExpression.IsKind(SyntaxKind.IdentifierName) ||
                    argumentExpression.IsKind(SyntaxKind.SimpleMemberAccessExpression))
                {
                    var argRefFunction = functionData.BodyFunctionReferences.FirstOrDefault(o => argument.Equals(o.ReferenceNode));
                    if (argRefFunction == null)
                    {
                        // Ignore only if the async argument does not match
                        // TODO: internal methods, unify with CalculateFunctionArguments
                        if (functionReferenceData.ReferenceFunctionData == null && delegateParams[i])                         // If the parameter is a delegate check the symbol of the argument
                        {
                            var argSymbol = documentData.SemanticModel.GetSymbolInfo(argumentExpression).Symbol;
                            if (argSymbol is ILocalSymbol arglocalSymbol)
                            {
                                // TODO: local arguments
                                functionReferenceData.Ignore(IgnoreReason.NotSupported("Local delegate arguments are currently not supported"));
                                return;
                            }
                            if (argSymbol is IMethodSymbol argMethodSymbol)
                            {
                                // TODO: support custom async counterparts that have different parameters
                                // If the invocation has at least one argument that does not fit into any async counterparts we have to ignore it
                                if (functionReferenceData.ReferenceAsyncSymbols
                                    .Where(o => o.Parameters.Length >= methodSymbol.Parameters.Length)                                     // The async counterpart may have less parameters. e.g. Parallel.For -> Task.WhenAll
                                    .All(o => !((IMethodSymbol)o.Parameters[i].Type.GetMembers("Invoke").First()).ReturnType.Equals(argMethodSymbol.ReturnType)))
                                {
                                    functionReferenceData.Ignore(IgnoreReason.Custom("The delegate argument does not fit to any async counterparts", DiagnosticSeverity.Hidden));
                                    return;
                                }
                            }
                        }
                        continue;
                    }
                    functionReferenceData.AddDelegateArgument(new DelegateArgumentData(argRefFunction, i));
                    argRefFunction.ArgumentOfFunctionInvocation = functionReferenceData;
                }
            }

            SetAsyncCounterpart(functionReferenceData);

            CalculateLastInvocation(node, functionReferenceData);

            foreach (var analyzer in _configuration.InvocationExpressionAnalyzers)
            {
                analyzer.AnalyzeInvocationExpression(node, functionReferenceData, documentData.SemanticModel);
            }

            PropagateCancellationToken(functionReferenceData);
        }
        private void AnalyzeMethodReference(DocumentData documentData, BodyFunctionDataReference refData)
        {
            var nameNode = refData.ReferenceNameNode;

            // Find the actual usage of the method
            SyntaxNode currNode = nameNode;
            var        ascend   = true;

            if (refData.ReferenceSymbol.IsPropertyAccessor())
            {
                ascend = false;
                AnalyzeAccessor(documentData, nameNode, refData);
            }
            else
            {
                currNode = nameNode.Parent;
            }

            while (ascend)
            {
                ascend = false;
                switch (currNode.Kind())
                {
                case SyntaxKind.ConditionalExpression:
                    break;

                case SyntaxKind.InvocationExpression:
                    AnalyzeInvocationExpression(documentData, (InvocationExpressionSyntax)currNode, refData);
                    break;

                case SyntaxKind.Argument:
                    AnalyzeArgumentExpression((ArgumentSyntax)currNode, nameNode, refData);
                    break;

                case SyntaxKind.AddAssignmentExpression:
                    refData.Ignore(IgnoreReason.Custom(
                                       $"Cannot attach an async method to an event (void async is not an option as cannot be awaited)", DiagnosticSeverity.Info));
                    break;

                case SyntaxKind.SubtractAssignmentExpression:
                    refData.Ignore(IgnoreReason.Custom($"Cannot detach an async method to an event", DiagnosticSeverity.Info));
                    break;

                case SyntaxKind.VariableDeclaration:
                    refData.Ignore(IgnoreReason.NotSupported($"Assigning async method to a variable is not supported"));
                    break;

                case SyntaxKind.CastExpression:
                    refData.AwaitInvocation = true;
                    ascend = true;
                    break;

                case SyntaxKind.ReturnStatement:
                    break;

                case SyntaxKind.ArrayInitializerExpression:
                case SyntaxKind.CollectionInitializerExpression:
                case SyntaxKind.ComplexElementInitializerExpression:
                    refData.Ignore(IgnoreReason.NotSupported($"Async method inside an array/collection initializer is not supported"));
                    break;

                // skip
                case SyntaxKind.VariableDeclarator:
                case SyntaxKind.EqualsValueClause:
                case SyntaxKind.SimpleMemberAccessExpression:
                case SyntaxKind.ArgumentList:
                case SyntaxKind.ObjectCreationExpression:
                case SyntaxKind.MemberBindingExpression:                         // ?.
                    ascend = true;
                    break;

                default:
                    throw new NotSupportedException(
                              $"Unknown node kind: {currNode.Kind()} at {currNode?.SyntaxTree.GetLineSpan(currNode.Span)}. Node:{Environment.NewLine}{currNode}");
                }

                if (ascend)
                {
                    currNode = currNode.Parent;
                }
            }
            refData.ReferenceNode = currNode;
            if (!refData.AwaitInvocation.HasValue)
            {
                refData.AwaitInvocation = refData.Conversion != ReferenceConversion.Ignore;
            }
        }