private void FindAsyncCounterparts(BodyFunctionDataReference functionReferenceData)
        {
            var methodSymbol = functionReferenceData.ReferenceSymbol;

            methodSymbol = methodSymbol.ReducedFrom ?? methodSymbol;             // System.Linq extensions

            functionReferenceData.ReferenceAsyncSymbols = new HashSet <IMethodSymbol>(GetAsyncCounterparts(methodSymbol.OriginalDefinition,
                                                                                                           functionReferenceData.InvokedFromType, _searchOptions)
                                                                                      .Where(o => _configuration.IgnoreAsyncCounterpartsPredicates.All(p => !p(o))));
        }
        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]);
        }
Esempio n. 3
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 static void LogReferenceDiagnostics(BodyFunctionDataReference data, List <KeyValuePair <DiagnosticSeverity, string> > logs)
        {
            if (data.IgnoredReason != null)
            {
                logs.Add(new KeyValuePair <DiagnosticSeverity, string>(data.IgnoredReason.DiagnosticSeverity,
                                                                       $"Method reference {data.ReferenceSymbol.Name} {data.ReferenceLocation.Location.GetLineSpan().Span.Format()} ignored reason: {data.IgnoredReason.Reason}"));
            }

            foreach (var diagnostic in data.GetDiagnostics())
            {
                logs.Add(new KeyValuePair <DiagnosticSeverity, string>(diagnostic.DiagnosticSeverity,
                                                                       $"Method reference {data.ReferenceSymbol.Name} {data.ReferenceLocation.Location.GetLineSpan().Span.Format()}: {diagnostic.Description}"));
            }
        }
        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);
        }
        /// <summary>
        /// Propagate CancellationTokenRequired to the method data only if the invocation can be async and the method does not have any external related methods (eg. external interface)
        /// </summary>
        private void PropagateCancellationToken(BodyFunctionDataReference functionReferenceData)
        {
            var methodData = functionReferenceData.Data.GetMethodOrAccessorData();

            if (functionReferenceData.Conversion != ReferenceConversion.ToAsync || methodData.ExternalRelatedMethods.Any())
            {
                return;
            }
            if (functionReferenceData.PassCancellationToken)
            {
                methodData.CancellationTokenRequired = true;
            }
            // Propagate if there is at least one async invocation inside an anoymous function that is passed as an argument to this invocation
            else if (functionReferenceData.DelegateArguments != null && functionReferenceData.DelegateArguments
                     .Where(o => o.FunctionData != null)
                     .Any(o => o.FunctionData.BodyFunctionReferences.Any(r => r.GetConversion() == ReferenceConversion.ToAsync && r.PassCancellationToken)))
            {
                methodData.CancellationTokenRequired = true;
            }
        }
        private async Task ScanAllMethodReferenceLocations(IMethodSymbol methodSymbol, int depth, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (cancellationToken.IsCancellationRequested)
            {
                cancellationToken.ThrowIfCancellationRequested();
            }
            methodSymbol = methodSymbol.OriginalDefinition;
            if ((!_configuration.SearchForMethodReferences(methodSymbol) && !_mustScanForMethodReferences.Contains(methodSymbol)) ||
                !_searchedMethodReferences.TryAdd(methodSymbol))
            {
                return;
            }
            // FindReferencesAsync will not search just for the passed method symbol but also for all its overrides, interfaces and external interfaces
            // so we need to add all related symbols to the searched list in order to avoid scanning those related members in the future calls
            // If any of the related symbols was already searched then we know that all references of the current method were already found
            var alreadyScanned = false;

            foreach (var relatedMethod in GetAllRelatedMethods(methodSymbol))
            {
                if (!_searchedMethodReferences.TryAdd(relatedMethod))
                {
                    alreadyScanned = true;
                }
            }
            if (alreadyScanned)
            {
                return;
            }

            var references = await SymbolFinder.FindReferencesAsync(methodSymbol, _solution, _analyzeDocuments, cancellationToken).ConfigureAwait(false);

            depth++;
            if (depth > _maxScanningDepth)
            {
                _maxScanningDepth = depth;
            }
            foreach (var refLocation in references.SelectMany(o => o.Locations))
            {
                if (_scannedLocationsSymbols.Contains(refLocation))
                {
                    continue;
                }
                _scannedLocationsSymbols.TryAdd(refLocation);

                if (refLocation.Document.Project != ProjectData.Project)
                {
                    throw new InvalidOperationException($"Reference {refLocation} is located in a document from another project");
                }

                var documentData = ProjectData.GetDocumentData(refLocation.Document);
                if (documentData == null)
                {
                    continue;
                }
                var symbol = documentData.GetEnclosingSymbol(refLocation);
                if (symbol == null)
                {
                    documentData.AddDiagnostic($"Symbol not found for reference ${refLocation}", DiagnosticSeverity.Hidden);
                    continue;
                }

                if (symbol.Kind != SymbolKind.Method)
                {
                    TryLinkToRealReference(symbol, documentData, refLocation);
                    continue;
                }

                var baseMethodData = documentData.GetFunctionData(symbol);
                if (baseMethodData == null)                 // TODO: Current is null for lambda in fields
                {
                    var refMethodSymbol = (IMethodSymbol)symbol;
                    if (refMethodSymbol.MethodKind == MethodKind.AnonymousFunction || refMethodSymbol.MethodKind == MethodKind.LambdaMethod)
                    {
                        documentData.AddDiagnostic(
                            $"Function inside member {refMethodSymbol.ContainingSymbol} cannot be async because of its kind {refMethodSymbol.MethodKind}",
                            DiagnosticSeverity.Hidden);
                    }
                    else
                    {
                        documentData.AddDiagnostic(
                            $"Method {refMethodSymbol} cannot be async because of its kind {refMethodSymbol.MethodKind}",
                            DiagnosticSeverity.Hidden);
                    }
                    continue;
                }

                // Find the real method on that reference as FindReferencesAsync will also find references to base and interface methods
                // Save the reference as it can be made async
                var nameNode = baseMethodData.GetNode().GetSimpleName(refLocation.Location.SourceSpan);
                if (nameNode == null)
                {
                    continue;                     // Can happen for a foreach token
                }
                var referenceSymbolInfo   = documentData.SemanticModel.GetSymbolInfo(nameNode);
                var referenceSymbol       = referenceSymbolInfo.Symbol;
                var methodReferenceSymbol = referenceSymbol as IMethodSymbol;
                if (methodReferenceSymbol == null && referenceSymbol is IPropertySymbol propertyReferenceSymbol)
                {
                    // We need to find the usage of the property, if getter or setter is used
                    methodReferenceSymbol = nameNode.IsAssigned()
                                                ? propertyReferenceSymbol.SetMethod
                                                : propertyReferenceSymbol.GetMethod;
                }
                if (methodReferenceSymbol == null)
                {
                    // Check if the node is inside a nameof keyword as GetSymbolInfo will never return a symbol for it only candidates
                    if (nameNode.IsInsideNameOf())
                    {
                        var referencedFuncs = new Dictionary <IMethodSymbol, FunctionData>();
                        foreach (var candidateSymbol in referenceSymbolInfo.CandidateSymbols.OfType <IMethodSymbol>())
                        {
                            var nameofReferenceData = ProjectData.GetFunctionData(candidateSymbol);
                            referencedFuncs.Add(candidateSymbol, nameofReferenceData);
                        }
                        var nameofReference = new NameofFunctionDataReference(baseMethodData, refLocation, nameNode, referencedFuncs, true);
                        if (!baseMethodData.References.TryAdd(nameofReference))
                        {
                            _logger.Debug($"Performance hit: MembersReferences {nameNode} already added");
                        }
                        foreach (var referencedFun in referencedFuncs.Values.Where(o => o != null))
                        {
                            referencedFun.SelfReferences.TryAdd(nameofReference);
                        }
                        continue;
                    }

                    methodReferenceSymbol = TryFindCandidate(nameNode, referenceSymbolInfo, documentData.SemanticModel);
                    if (methodReferenceSymbol == null)
                    {
                        throw new InvalidOperationException($"Unable to find symbol for node {nameNode} inside function {baseMethodData.Symbol}");
                    }
                    documentData.AddDiagnostic(
                        $"GetSymbolInfo did not successfully resolved symbol for node {nameNode} inside function " +
                        $"{baseMethodData.Symbol.Name} {baseMethodData.GetLineSpan().Span.Format()}, " +
                        $"but we got a candidate instead. CandidateReason: {referenceSymbolInfo.CandidateReason}",
                        DiagnosticSeverity.Info);
                }
                var referenceFunctionData = ProjectData.GetFunctionData(methodReferenceSymbol);
                // Check if the reference is a cref reference or a nameof
                if (nameNode.IsInsideCref())
                {
                    var crefReference = new CrefFunctionDataReference(baseMethodData, refLocation, nameNode, methodReferenceSymbol, referenceFunctionData, true);
                    if (!baseMethodData.References.TryAdd(crefReference))
                    {
                        _logger.Debug($"Performance hit: MembersReferences {nameNode} already added");
                    }
                    referenceFunctionData?.SelfReferences.TryAdd(crefReference);
                    continue;                     // No need to further scan a cref reference
                }
                var methodReferenceData = new BodyFunctionDataReference(baseMethodData, refLocation, nameNode, methodReferenceSymbol, referenceFunctionData);
                if (!baseMethodData.References.TryAdd(methodReferenceData))
                {
                    _logger.Debug($"Performance hit: method reference {methodReferenceSymbol} already processed");
                    continue;                     // Reference already processed
                }
                referenceFunctionData?.SelfReferences.TryAdd(methodReferenceData);

                if (baseMethodData.Conversion == MethodConversion.Ignore)
                {
                    continue;
                }
                // Do not scan for method that will be only copied (e.g. the containing type is a new type).
                if (baseMethodData.Conversion == MethodConversion.Copy)
                {
                    continue;
                }

                if (baseMethodData is MethodOrAccessorData methodData && !_scannedMethodOrAccessors.Contains(methodData))
                {
                    await ScanMethodData(methodData, depth, cancellationToken).ConfigureAwait(false);
                }
                // Scan a local/anonymous function only if there is a chance that can be called elsewere. (e.g. saved to a variable or local function)
                // TODO: support local variables
                if (baseMethodData is LocalFunctionData)
                {
                    await ScanAllMethodReferenceLocations(baseMethodData.Symbol, depth, cancellationToken).ConfigureAwait(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 bool SetAsyncCounterpart(BodyFunctionDataReference functionReferenceData)
        {
            var methodSymbol = functionReferenceData.ReferenceSymbol;

            methodSymbol = methodSymbol.ReducedFrom ?? methodSymbol;             // System.Linq extensions
            var useTokens = _configuration.UseCancellationTokens | _configuration.CanScanForMissingAsyncMembers != null;

            if (functionReferenceData.ReferenceAsyncSymbols.Any())
            {
                if (functionReferenceData.ReferenceAsyncSymbols.All(o => o.ReturnsVoid || !o.ReturnType.IsTaskType()))
                {
                    functionReferenceData.AwaitInvocation = false;
                    functionReferenceData.AddDiagnostic("Cannot await method that is either void or do not return a Task", DiagnosticSeverity.Hidden);
                }

                var passToken         = false;
                var analyzationResult = AnalyzeAsyncCandidates(functionReferenceData, functionReferenceData.ReferenceAsyncSymbols.ToList(), useTokens);
                if (analyzationResult.AsyncCandidate != null)
                {
                    passToken = analyzationResult.AsyncCandidate.Parameters.Any(o => o.Type.IsCancellationToken());
                }

                if (analyzationResult.IgnoreDelegateArgumentsReason != null)
                {
                    foreach (var delegateArgument in functionReferenceData.DelegateArguments)
                    {
                        delegateArgument.FunctionData?.Copy();
                        delegateArgument.FunctionReference?.Ignore(analyzationResult.IgnoreDelegateArgumentsReason);
                    }
                }

                if (analyzationResult.IgnoreBodyFunctionDataReferenceReason != null)
                {
                    functionReferenceData.Ignore(analyzationResult.IgnoreBodyFunctionDataReferenceReason);
                    return(false);
                }

                if (analyzationResult.AsyncCandidate != null)
                {
                    functionReferenceData.PassCancellationToken  = passToken;
                    functionReferenceData.AsyncCounterpartSymbol = analyzationResult.AsyncCandidate;
                    functionReferenceData.AsyncCounterpartName   = analyzationResult.AsyncCandidate.Name;
                }
                else
                {
                    return(false);
                }

                if (functionReferenceData.AsyncCounterpartSymbol != null &&
                    functionReferenceData.ArgumentOfFunctionInvocation == null &&
                    analyzationResult.CanBeAsync)
                {
                    if (functionReferenceData.AsyncCounterpartSymbol.IsObsolete())
                    {
                        functionReferenceData.Ignore(IgnoreReason.CallObsoleteMethod);
                    }
                    else
                    {
                        functionReferenceData.ToAsync();
                    }
                }

                // Ignore the method if we found its async counterpart
                if (functionReferenceData.ReferenceFunctionData is MethodOrAccessorData methodOrAccessorData)
                {
                    if (passToken)
                    {
                        methodOrAccessorData.AsyncCounterpartWithTokenSymbol = analyzationResult.AsyncCandidate;
                    }
                    else
                    {
                        methodOrAccessorData.AsyncCounterpartSymbol = analyzationResult.AsyncCandidate;
                    }
                    methodOrAccessorData.Ignore(IgnoreReason.AsyncCounterpartExists);
                }
            }
            else if (!ProjectData.Contains(methodSymbol))
            {
                // If we are dealing with an external method and there are no async counterparts for it, we cannot convert it to async
                functionReferenceData.Ignore(IgnoreReason.NoAsyncCounterparts);
                return(false);
            }
            else if (functionReferenceData.ReferenceFunctionData != null)
            {
                functionReferenceData.AsyncCounterpartName   = functionReferenceData.ReferenceFunctionData.AsyncCounterpartName;
                functionReferenceData.AsyncCounterpartSymbol = methodSymbol;
            }
            return(true);
        }
        private void AnalyzeArgumentExpression(ArgumentSyntax node, SimpleNameSyntax nameNode, BodyFunctionDataReference result)
        {
            var documentData      = result.Data.TypeData.NamespaceData.DocumentData;
            var methodArgTypeInfo = documentData.SemanticModel.GetTypeInfo(nameNode);

            if (methodArgTypeInfo.ConvertedType?.TypeKind != TypeKind.Delegate)
            {
                // TODO: debug and document
                return;
            }
            result.AwaitInvocation = false;             // we cannot await something that is not invoked

            var delegateMethod = (IMethodSymbol)methodArgTypeInfo.ConvertedType.GetMembers("Invoke").First();

            if (!delegateMethod.IsAsync)
            {
                FindAsyncCounterparts(result);
                if (!SetAsyncCounterpart(result))
                {
                    return;
                }
                PropagateCancellationToken(result);

                // Check if the method is passed as an argument to a candidate method
                //var invokedByMethod = result.FunctionData.BodyMethodReferences
                //	.Where(o => o.ReferenceNode.IsKind(SyntaxKind.InvocationExpression))
                //	.Select(o => new
                //	{
                //		Reference = o,
                //		Index = ((InvocationExpressionSyntax) o.ReferenceNode).ArgumentList.Arguments.IndexOf(node)
                //	})
                //	.FirstOrDefault(o => o.Index >= 0);
                //if (invokedByMethod == null)
                //{
                //	result.Ignore($"Cannot pass an async method as argument to a non async Delegate argument:\r\n{delegateMethod}\r\n");
                //	Logger.Warn(result.IgnoredReason);
                //	return;
                //}
                //invokedByMethod.Reference.FunctionArguments.Add(new FunctionArgumentData(result, invokedByMethod.Index));
                //result.ArgumentOfFunctionInvocation = invokedByMethod.Reference;
            }
            else
            {
                result.Ignore(IgnoreReason.AlreadyAsync);

                //var argumentMethodSymbol = (IMethodSymbol)documentData.SemanticModel.GetSymbolInfo(nameNode).Symbol;
                //if (!argumentMethodSymbol.ReturnType.IsAwaitRequired(delegateMethod.ReturnType)) // i.e IList<T> -> IEnumerable<T>
                //{
                //	result.AwaitInvocation = true;
                //}
            }
        }
        private void CalculateLastInvocation(SyntaxNode node, BodyFunctionDataReference functionReferenceData)
        {
            var functionData     = functionReferenceData.Data;
            var methodSymbol     = functionReferenceData.ReferenceSymbol;
            var functionBodyNode = functionData.GetBodyNode();

            if (functionBodyNode == null)
            {
                return;
            }
            // Check if the invocation node is returned in an expression body
            if (node.Parent.Equals(functionBodyNode) ||      //eg. bool ExpressionReturn() => SimpleFile.Write();
                node.Equals(functionBodyNode) ||             // eg. Func<bool> fn = () => SimpleFile.Write();
                (
                    node.IsKind(SyntaxKind.IdentifierName) &&
                    node.Parent.Parent.IsKind(SyntaxKind.ArrowExpressionClause) &&
                    node.Parent.Parent.Equals(functionBodyNode)
                )                 // eg. bool Prop => StaticClass.Property;
                )
            {
                functionReferenceData.LastInvocation   = true;
                functionReferenceData.UseAsReturnValue = !methodSymbol.ReturnsVoid;
                return;
            }
            if (!functionBodyNode.IsKind(SyntaxKind.Block))
            {
                return;                 // The invocation node is inside an expression body but is not the last statement
            }
            // Check if the invocation is the last statement to be executed inside the method
            SyntaxNode      currNode  = node;
            StatementSyntax statement = null;

            while (!currNode.Equals(functionBodyNode))
            {
                currNode = currNode.Parent;
                switch (currNode.Kind())
                {
                case SyntaxKind.ReturnStatement:
                    functionReferenceData.LastInvocation   = true;
                    functionReferenceData.UseAsReturnValue = true;
                    return;

                case SyntaxKind.ConditionalExpression:                         // return num > 5 ? SimpleFile.Write() : false
                    var conditionExpression = (ConditionalExpressionSyntax)currNode;
                    if (conditionExpression.Condition.Contains(node))
                    {
                        return;
                    }
                    continue;

                case SyntaxKind.IfStatement:
                    var ifStatement = (IfStatementSyntax)currNode;
                    if (ifStatement.Condition.Contains(node))
                    {
                        return;
                    }
                    statement = (StatementSyntax)currNode;
                    continue;

                case SyntaxKind.ElseClause:
                    continue;

                case SyntaxKind.ExpressionStatement:
                    statement = (StatementSyntax)currNode;
                    continue;

                case SyntaxKind.Block:
                    if (statement == null)
                    {
                        return;
                    }
                    // We need to check that the current statement is the last block statement
                    var block = (BlockSyntax)currNode;
                    if (!statement.Equals(block.Statements.Last()))
                    {
                        return;
                    }
                    statement = block;
                    continue;

                default:
                    return;
                }
            }
            functionReferenceData.LastInvocation = true;
            if (functionReferenceData.ReferenceFunctionData == null && functionReferenceData.AsyncCounterpartSymbol != null)
            {
                functionReferenceData.UseAsReturnValue = !_configuration.CanAlwaysAwait(methodSymbol) && functionReferenceData.AsyncCounterpartSymbol.ReturnType.IsTaskType();
            }
            else if (!methodSymbol.ReturnsVoid)
            {
                functionReferenceData.UseAsReturnValue = !_configuration.CanAlwaysAwait(methodSymbol);                 // here we don't now if the method will be converted to async or not
            }
        }
        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 AnalyzeAccessor(DocumentData documentData, SimpleNameSyntax node, BodyFunctionDataReference functionReferenceData)
        {
            var functionData = functionReferenceData.Data;
            var functionNode = functionData.GetNode();

            if (IgnoreIfInvalidAncestor(node, functionNode, functionReferenceData))
            {
                return;
            }
            functionReferenceData.InvokedFromType = functionData.Symbol.ContainingType;
            FindAsyncCounterparts(functionReferenceData);
            SetAsyncCounterpart(functionReferenceData);
            CalculateLastInvocation(node, functionReferenceData);
            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;
            }
        }
Esempio n. 15
0
        private bool SetAsyncCounterpart(BodyFunctionDataReference functionReferenceData)
        {
            var methodSymbol = functionReferenceData.ReferenceSymbol;

            methodSymbol = methodSymbol.ReducedFrom ?? methodSymbol;             // System.Linq extensions
            var useTokens = _configuration.UseCancellationTokens | _configuration.ScanForMissingAsyncMembers != null;

            if (functionReferenceData.ReferenceAsyncSymbols.Any())
            {
                if (functionReferenceData.ReferenceAsyncSymbols.All(o => o.ReturnsVoid || !o.ReturnType.IsTaskType()))
                {
                    functionReferenceData.AwaitInvocation = false;
                    functionReferenceData.AddDiagnostic("Cannot await method that is either void or do not return a Task", DiagnosticSeverity.Hidden);
                }
                IMethodSymbol asyncCounterpart = null;
                var           passToken        = useTokens;
                if (useTokens)
                {
                    var asyncCandidates = functionReferenceData.ReferenceAsyncSymbols
                                          .Where(o => o.Parameters.Length > methodSymbol.Parameters.Length)
                                          .ToList();
                    asyncCounterpart = FindAsyncCandidate(functionReferenceData, asyncCandidates);
                    if (asyncCandidates.Count > 1 && asyncCounterpart == null)
                    {
                        return(false);
                    }
                }
                // If token overload (optionally) was not found try to find a counterpart without token parameter
                if (asyncCounterpart == null)
                {
                    asyncCounterpart = FindAsyncCandidate(functionReferenceData, functionReferenceData.ReferenceAsyncSymbols.ToList());
                    passToken        = false;
                }
                if (asyncCounterpart != null)
                {
                    functionReferenceData.PassCancellationToken  = passToken;
                    functionReferenceData.AsyncCounterpartSymbol = asyncCounterpart;
                    functionReferenceData.AsyncCounterpartName   = asyncCounterpart.Name;
                }
                else
                {
                    return(false);
                }
                if (functionReferenceData.AsyncCounterpartSymbol != null)
                {
                    functionReferenceData.Conversion = ReferenceConversion.ToAsync;
                }
            }
            else if (!ProjectData.Contains(methodSymbol))
            {
                // If we are dealing with an external method and there are no async counterparts for it, we cannot convert it to async
                functionReferenceData.Ignore(IgnoreReason.NoAsyncCounterparts);
                return(false);
            }
            else if (functionReferenceData.ReferenceFunctionData != null)
            {
                functionReferenceData.AsyncCounterpartName   = functionReferenceData.ReferenceFunctionData.AsyncCounterpartName;
                functionReferenceData.AsyncCounterpartSymbol = methodSymbol;
            }
            return(true);
        }