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