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]); }
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; } }