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 PreAnalyzeFunctionData(FunctionData functionData, SemanticModel semanticModel) { var methodSymbol = functionData.Symbol; if (functionData.Conversion == MethodConversion.Ignore) { return; } functionData.Conversion = _configuration.GetMethodConversion(methodSymbol); // TODO: validate conversion if (functionData.Conversion == MethodConversion.Ignore) { functionData.Ignore(IgnoreReason.MethodConversion, true); return; } functionData.ForceAsync = functionData.Conversion.HasFlag(MethodConversion.ToAsync); var newType = functionData.TypeData.IsNewType; // Here we want to log only ignored methods that were explicitly set to async void IgnoreOrCopy(IgnoreReason reason) { if (newType) { functionData.Copy(); } else { if (functionData.ForceAsync) { reason = reason.WithSeverity(DiagnosticSeverity.Warning); } functionData.Ignore(reason); } } if (methodSymbol.IsAsync || methodSymbol.Name.EndsWith("Async")) { IgnoreOrCopy(IgnoreReason.AlreadyAsync); return; } if ( methodSymbol.MethodKind != MethodKind.Ordinary && methodSymbol.MethodKind != MethodKind.ExplicitInterfaceImplementation && methodSymbol.MethodKind != MethodKind.PropertyGet && methodSymbol.MethodKind != MethodKind.PropertySet) { IgnoreOrCopy(IgnoreReason.NotSupported($"Unsupported method kind {methodSymbol.MethodKind}")); return; } if (methodSymbol.Parameters.Any(o => o.RefKind == RefKind.Out)) { IgnoreOrCopy(IgnoreReason.OutParameters); return; } FillFunctionLocks(functionData, semanticModel); if (!(functionData is MethodOrAccessorData methodData)) { return; } // Override user configuration if the method is set to be copied on a partial type conversion if (methodData.Conversion == MethodConversion.Copy && !newType) { methodData.AddDiagnostic( "Method cannot be copied, when the containing type conversion is not set to be a new type. " + $"Override the method conversion to '{MethodConversion.Unknown}'", DiagnosticSeverity.Warning); methodData.Conversion = MethodConversion.Unknown; } // Check if explicitly implements external interfaces if (methodSymbol.MethodKind == MethodKind.ExplicitInterfaceImplementation) { foreach (var interfaceMember in methodSymbol.ExplicitInterfaceImplementations) { // Check if the interface member has an async counterpart var asyncConterparts = FillRelatedAsyncMethods(methodData, interfaceMember); if (methodSymbol.ContainingAssembly.Name != interfaceMember.ContainingAssembly.Name) { methodData.ExternalRelatedMethods.TryAdd(interfaceMember); if (!asyncConterparts.Any()) { IgnoreOrCopy(IgnoreReason.ExplicitImplementsExternalMethodWithoutAsync(interfaceMember)); return; } } else { methodData.ImplementedInterfaces.TryAdd(interfaceMember); } // For new types we need to copy all interface members if (newType) { methodData.SoftCopy(); } } } // Check if the method is overriding an external method var overridenMethod = methodSymbol.OverriddenMethod; while (overridenMethod != null) { // Check if the member has an async counterpart that is not implemented in the current type (missing member) var asyncConterparts = FillRelatedAsyncMethods(methodData, overridenMethod); if (methodSymbol.ContainingAssembly.Name != overridenMethod.ContainingAssembly.Name) { methodData.ExternalRelatedMethods.TryAdd(overridenMethod); if (!asyncConterparts.Any()) { IgnoreOrCopy(IgnoreReason.OverridesExternalMethodWithoutAsync(overridenMethod)); return; } } else { 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; var interfaceMembers = 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>() .ToList(); foreach (var interfaceMember in interfaceMembers) { // Check if the member has an async counterpart that is not implemented in the current type (missing member) var asyncConterparts = FillRelatedAsyncMethods(methodData, interfaceMember); if (methodSymbol.ContainingAssembly.Name != interfaceMember.ContainingAssembly.Name) { // Check if there is an internal interface member that hides this method if (interfaceMembers.Where(o => o.ContainingAssembly.Name == methodSymbol.ContainingAssembly.Name) .Any(o => o.GetHiddenMethods().Any(hm => hm.Equals(interfaceMember)))) { continue; } methodData.ExternalRelatedMethods.TryAdd(interfaceMember); if (!asyncConterparts.Any()) { IgnoreOrCopy(IgnoreReason.ImplementsExternalMethodWithoutAsync(interfaceMember)); return; } } else { methodData.ImplementedInterfaces.TryAdd(interfaceMember); } // For new types we need to copy all interface member if (newType) { methodData.SoftCopy(); } } // 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 asyncCounterparts = GetAsyncCounterparts(methodSymbol.OriginalDefinition, _preAnalyzeSearchOptions).ToList(); if (asyncCounterparts.Any()) { if (!_preAnalyzeSearchOptions.HasFlag(AsyncCounterpartsSearchOptions.HasCancellationToken) && 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 (_preAnalyzeSearchOptions.HasFlag(AsyncCounterpartsSearchOptions.HasCancellationToken) && 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; } methodData.IgnoreAsyncCounterpart = _configuration.IgnoreAsyncCounterpartsPredicates.Any(p => p(asyncCounterpart)); } // TODO: define a better logic // We should not ignore if none of the async counterparts is in the sync method type. if (asyncCounterparts.Any(o => o.ContainingType.Equals(methodSymbol.ContainingType)) /*(_configuration.UseCancellationTokens && asyncCounterparts.Count == 2) || * (!_configuration.UseCancellationTokens && asyncCounterparts.Count == 1)*/ ) { IgnoreOrCopy(IgnoreReason.AsyncCounterpartExists); methodData.CancellationTokenRequired = methodData.AsyncCounterpartWithTokenSymbol != null; return; } } // Create an override async method if any of the related async methods is virtual or abstract // We need to do this here so that the method body will get scanned for async counterparts if (methodData.RelatedAsyncMethods.Any(o => o.IsVirtual || o.IsAbstract) && _configuration.CanScanForMissingAsyncMembers?.Invoke(methodData.TypeData.Symbol) == true) { if (!methodData.Conversion.HasAnyFlag(MethodConversion.ToAsync, MethodConversion.Smart)) { methodData.Smart(); } if (newType) { methodData.SoftCopy(); } // We have to generate the cancellation token parameter if the async member has more parameters that the sync counterpart var asyncMember = methodData.RelatedAsyncMethods .Where(o => o.IsVirtual || o.IsAbstract) .FirstOrDefault(o => o.Parameters.Length > methodData.Symbol.Parameters.Length); if (asyncMember != null) { methodData.CancellationTokenRequired = true; // We suppose that the cancellation token is the last parameter methodData.MethodCancellationToken = asyncMember.Parameters.Last().HasExplicitDefaultValue ? MethodCancellationToken.Optional : MethodCancellationToken.Required; } } }
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; } }