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