private void AnalyzeInvocationExpression(DocumentData documentData, InvocationExpressionSyntax node, InvokeFunctionReferenceData functionReferenceData)
        {
            var functionData     = functionReferenceData.FunctionData;
            var methodSymbol     = functionReferenceData.ReferenceSymbol;
            var functionNode     = functionData.GetNode();
            var functionBodyNode = functionData.GetBodyNode();
            var queryExpression  = node.Ancestors()
                                   .TakeWhile(o => o != functionNode)
                                   .OfType <QueryExpressionSyntax>()
                                   .FirstOrDefault();

            if (queryExpression != null)             // Await is not supported in a linq query
            {
                functionReferenceData.Ignore = true;
                Logger.Warn($"Cannot await async method in a query expression:\r\n{queryExpression}\r\n");
                return;
            }

            var searchOptions = AsyncCounterpartsSearchOptions.Default;

            if (_configuration.UseCancellationTokenOverload)
            {
                searchOptions |= AsyncCounterpartsSearchOptions.HasCancellationToken;
            }
            functionReferenceData.ReferenceAsyncSymbols = new HashSet <IMethodSymbol>(GetAsyncCounterparts(methodSymbol.OriginalDefinition, searchOptions));
            if (functionReferenceData.ReferenceAsyncSymbols.Any())
            {
                if (functionReferenceData.ReferenceAsyncSymbols.All(o => o.ReturnsVoid || !o.ReturnType.IsTaskType()))
                {
                    functionReferenceData.AwaitInvocation = false;
                    Logger.Info($"Cannot await method that is either void or do not return a Task:\r\n{methodSymbol}\r\n");
                }
                var nameGroups = functionReferenceData.ReferenceAsyncSymbols.GroupBy(o => o.Name).ToList();
                if (nameGroups.Count == 1)
                {
                    functionReferenceData.AsyncCounterpartName = nameGroups[0].Key;
                }
            }
            else if (!ProjectData.Contains(functionReferenceData.ReferenceSymbol))
            {
                // If we are dealing with an external method and there are no async counterparts for it, we cannot convert it to async
                functionReferenceData.Ignore = true;
                Logger.Info($"Method {methodSymbol} can not be async as there is no async counterparts for it");
                return;
            }
            else if (functionReferenceData.ReferenceFunctionData != null)
            {
                functionReferenceData.AsyncCounterpartName = functionReferenceData.ReferenceSymbol.Name + "Async";
            }

            // 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;
                    Logger.Info(
                        $"Cannot await invocation of a method that returns a Task without be synchronously awaited:\r\n{methodSymbol}\r\n");
                }
                else
                {
                    functionReferenceData.SynchronouslyAwaited = true;
                }
            }

            if (node.Parent.IsKind(SyntaxKind.ReturnStatement))
            {
                functionReferenceData.UseAsReturnValue = true;
            }

            // Calculate if node is the last statement
            if (node.Parent.Equals(functionBodyNode) ||       //eg. bool ExpressionReturn() => SimpleFile.Write();
                node.Equals(functionBodyNode)                 // eg. Func<bool> fn = () => SimpleFile.Write();
                )
            {
                functionReferenceData.LastInvocation   = true;
                functionReferenceData.UseAsReturnValue = !methodSymbol.ReturnsVoid;
            }
            var bodyBlock = functionBodyNode as BlockSyntax;

            if (bodyBlock?.Statements.Last() == node.Parent)
            {
                functionReferenceData.LastInvocation = true;
            }

            // Set CancellationTokenRequired if we detect that one of the async counterparts has a cancellation token as a parameter
            if (_configuration.UseCancellationTokenOverload &&
                functionReferenceData.ReferenceAsyncSymbols.Any(o => o.Parameters.Length > methodSymbol.Parameters.Length))
            {
                functionReferenceData.CancellationTokenRequired = true;
            }

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

            // Propagate CancellationTokenRequired to the method data only if the invocation can be async
            if (functionReferenceData.CancellationTokenRequired && functionReferenceData.GetConversion() == ReferenceConversion.ToAsync)
            {
                // We need to set CancellationTokenRequired to true for the method that contains this invocation
                var methodData = functionReferenceData.FunctionData.GetMethodData();
                methodData.CancellationTokenRequired = true;
            }
        }
Example #2
0
        private void PreAnalyzeMethodData(MethodData methodData)
        {
            var methodSymbol = methodData.Symbol;

            methodData.Conversion = _configuration.MethodConversionFunction(methodSymbol);
            if (methodData.Conversion == MethodConversion.Ignore)
            {
                Logger.Debug($"Method {methodSymbol} will be ignored because of MethodConversionFunction");
                return;
            }

            var forceAsync = methodData.Conversion == MethodConversion.ToAsync;
            var log        = forceAsync ? Logger.Warn : (Action <object>)Logger.Debug;

            if (methodSymbol.IsAsync || methodSymbol.Name.EndsWith("Async"))
            {
                log($"Symbol {methodSymbol} is already async");
                methodData.Conversion = MethodConversion.Ignore;
                methodData.IsAsync    = true;
                return;
            }
            if (!ProjectData.Contains(methodSymbol))
            {
                log($"Method {methodSymbol} is external and cannot be made async");
                methodData.Conversion = MethodConversion.Ignore;
                return;
            }
            if (methodSymbol.MethodKind != MethodKind.Ordinary && methodSymbol.MethodKind != MethodKind.ExplicitInterfaceImplementation)
            {
                log($"Method {methodSymbol} is a {methodSymbol.MethodKind} and cannot be made async");
                methodData.Conversion = MethodConversion.Ignore;
                return;
            }

            if (methodSymbol.Parameters.Any(o => o.RefKind == RefKind.Out))
            {
                log($"Method {methodSymbol} has out parameters and cannot be made async");
                methodData.Conversion = MethodConversion.Ignore;
                return;
            }

            // Check if explicitly implements external interfaces
            if (methodSymbol.MethodKind == MethodKind.ExplicitInterfaceImplementation)
            {
                foreach (var interfaceMember in methodSymbol.ExplicitInterfaceImplementations)
                {
                    if (methodSymbol.ContainingAssembly.Name != interfaceMember.ContainingAssembly.Name)
                    {
                        methodData.ExternalRelatedMethods.TryAdd(interfaceMember);

                        // Check if the interface member has an async counterpart
                        var asyncConterPart = interfaceMember.ContainingType.GetMembers()
                                              .OfType <IMethodSymbol>()
                                              .Where(o => o.Name == methodSymbol.Name + "Async")
                                              .SingleOrDefault(o => methodSymbol.IsAsyncCounterpart(o, true, false, false));

                        if (asyncConterPart == null)
                        {
                            log($"Method {methodSymbol} implements an external interface {interfaceMember} and cannot be made async");
                            methodData.Conversion = MethodConversion.Ignore;
                            return;
                        }
                        methodData.ExternalAsyncMethods.TryAdd(asyncConterPart);
                    }
                    else
                    {
                        methodData.ImplementedInterfaces.TryAdd(interfaceMember);
                    }
                    //var syntax = interfaceMember.DeclaringSyntaxReferences.FirstOrDefault();
                    //if (!CanProcessSyntaxReference(syntax))
                    //{
                    //	continue;
                    //}
                }
            }

            // Check if the method is overriding an external method
            var overridenMethod = methodSymbol.OverriddenMethod;

            while (overridenMethod != null)
            {
                if (methodSymbol.ContainingAssembly.Name != overridenMethod.ContainingAssembly.Name)
                {
                    methodData.ExternalRelatedMethods.TryAdd(overridenMethod);
                    // Check if the external member has an async counterpart that is not implemented in the current type (missing member)
                    var asyncConterPart = overridenMethod.ContainingType.GetMembers()
                                          .OfType <IMethodSymbol>()
                                          .Where(o => o.Name == methodSymbol.Name + "Async" && !o.IsSealed && (o.IsVirtual || o.IsAbstract || o.IsOverride))
                                          .SingleOrDefault(o => methodSymbol.IsAsyncCounterpart(o, true, false, false));
                    if (asyncConterPart == null)
                    {
                        log(
                            $"Method {methodSymbol} overrides an external method {overridenMethod} that has not an async counterpart... method will not be converted");
                        methodData.Conversion = MethodConversion.Ignore;
                        return;
                        //if (!asyncMethods.Any() || (asyncMethods.Any() && !overridenMethod.IsOverride && !overridenMethod.IsVirtual))
                        //{
                        //	Logger.Warn($"Method {methodSymbol} overrides an external method {overridenMethod} and cannot be made async");
                        //	return MethodSymbolAnalyzeResult.Invalid;
                        //}
                    }
                    methodData.ExternalAsyncMethods.TryAdd(asyncConterPart);
                }
                else
                {
                    methodData.OverridenMethods.TryAdd(overridenMethod);
                }
                //var syntax = overridenMethod.DeclaringSyntaxReferences.SingleOrDefault();
                //else if (CanProcessSyntaxReference(syntax))
                //{
                //	methodData.OverridenMethods.TryAdd(overridenMethod);
                //}
                if (overridenMethod.OverriddenMethod != null)
                {
                    overridenMethod = overridenMethod.OverriddenMethod;
                }
                else
                {
                    break;
                }
            }
            methodData.BaseOverriddenMethod = overridenMethod;

            // Check if the method is implementing an external interface, if true skip as we cannot modify externals
            // FindImplementationForInterfaceMember will find the first implementation method starting from the deepest base class
            var type = methodSymbol.ContainingType;

            foreach (var interfaceMember in type.AllInterfaces
                     .SelectMany(
                         o => o.GetMembers(methodSymbol.Name)
                         .Where(
                             m =>
            {
                // Find out if the method implements the interface member or an override
                // method that implements it
                var impl = type.FindImplementationForInterfaceMember(m);
                return(methodSymbol.Equals(impl) || methodData.OverridenMethods.Any(ov => ov.Equals(impl)));
            }
                             ))
                     .OfType <IMethodSymbol>())
            {
                if (methodSymbol.ContainingAssembly.Name != interfaceMember.ContainingAssembly.Name)
                {
                    methodData.ExternalRelatedMethods.TryAdd(interfaceMember);

                    // Check if the member has an async counterpart that is not implemented in the current type (missing member)
                    var asyncConterPart = interfaceMember.ContainingType.GetMembers()
                                          .OfType <IMethodSymbol>()
                                          .Where(o => o.Name == methodSymbol.Name + "Async")
                                          .SingleOrDefault(o => methodSymbol.IsAsyncCounterpart(o, true, false, false));
                    if (asyncConterPart == null)
                    {
                        log($"Method {methodSymbol} implements an external interface {interfaceMember} and cannot be made async");
                        methodData.Conversion = MethodConversion.Ignore;
                        return;
                    }
                    methodData.ExternalAsyncMethods.TryAdd(asyncConterPart);
                }
                else
                {
                    methodData.ImplementedInterfaces.TryAdd(interfaceMember);
                }
                //var syntax = interfaceMember.DeclaringSyntaxReferences.SingleOrDefault();
                //if (!CanProcessSyntaxReference(syntax))
                //{
                //	continue;
                //}
            }

            // Verify if there is already an async counterpart for this method
            //TODO: this is not correct when generating methods with a cancellation token as here we do not know
            // if the generated method will have the cancellation token parameter or not
            var searchOptions = AsyncCounterpartsSearchOptions.EqualParameters | AsyncCounterpartsSearchOptions.IgnoreReturnType;

            if (_configuration.UseCancellationTokenOverload)
            {
                searchOptions |= AsyncCounterpartsSearchOptions.HasCancellationToken;
            }
            var asyncCounterparts = GetAsyncCounterparts(methodSymbol.OriginalDefinition, searchOptions).ToList();

            if (asyncCounterparts.Any())
            {
                if (!_configuration.UseCancellationTokenOverload && asyncCounterparts.Count > 1)
                {
                    throw new InvalidOperationException($"Method {methodSymbol} has more than one async counterpart");
                }
                // We shall get a maximum of two async counterparts when the HasCancellationToken flag is used
                if (_configuration.UseCancellationTokenOverload && asyncCounterparts.Count > 2)
                {
                    throw new InvalidOperationException($"Method {methodSymbol} has more than two async counterparts");
                }

                foreach (var asyncCounterpart in asyncCounterparts)
                {
                    // Check if the async counterpart has a cancellation token
                    if (asyncCounterpart.Parameters.Length > methodSymbol.Parameters.Length)
                    {
                        methodData.AsyncCounterpartWithTokenSymbol = asyncCounterpart;
                    }
                    else
                    {
                        methodData.AsyncCounterpartSymbol = asyncCounterpart;
                    }
                }

                if (
                    (_configuration.UseCancellationTokenOverload && asyncCounterparts.Count == 2) ||
                    (!_configuration.UseCancellationTokenOverload && asyncCounterparts.Count == 1)
                    )
                {
                    log($"Method {methodSymbol} has already an async counterpart {asyncCounterparts.First()}");
                    methodData.Conversion = MethodConversion.Ignore;
                    return;
                }
            }
        }
        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);
        }
Example #4
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);
        }