/// <summary> /// Returns true if an equivalent asynchronous method of the method used in /// the <paramref name="invocation"/> exists and is at the same time a /// potential candidate to be used exactly in that particular <paramref name="invocation"/>. /// </summary> /// <remarks> /// This method does not only check if an equivalent asynchronous method /// exists. In addition, it runs additional checks to see if it make sense /// to call such existing asynchronous equivalent in the particular <paramref name="invocation"/>. /// For example, the suggestion to await asynchronous equivalent /// makes sense only if the enclosing method /// within which the invocation happens can be turned into async method. /// For example, if the enclosing method is an interface implementation or an override /// method of an interface or base class that cannot be changed, than it cannot be /// turned into async method and thus the suggestion makes no sense. /// </remarks> public bool EquivalentAsynchronousCandidateExistsFor(InvocationExpressionSyntax invocation, SemanticModel semanticModel, EnclosingMethodAsyncStatus enclosingMethodAsyncStatus) { if (invocation.Expression == null) { return(false); } if (!InvokedMethodPotentiallyHasAsynchronousEquivalent(invocation)) { return(false); } if (!(semanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol method)) { return(false); } if (KnownMethodsToIgnore.Any(methodToIgnore => methodToIgnore.RepresentsMethod(method))) { return(false); } // So far we do not suggest turning lambdas and anonymous methods // into async. So we will at the moment just ignore that case. // TODO: Support suggestion for lambdas and anonymous methods. if (invocation.IsWithinLambdaOrAnonymousMethod()) { return(false); } // If type authors invoke the synchronous method // within the implementation of its containing type // we assume that they exactly know what they are doing. // They for sure want to call exactly that method on // that particular place in code. We are 100% sure that // they do not want to call its async equivalent. if (MethodIsInvokedWithinItsContainingType()) { return(false); } var enclosingMethodSymbol = GetEnclosingMethod(); if (enclosingMethodSymbol == null) { return(false); } if (enclosingMethodAsyncStatus == EnclosingMethodAsyncStatus.EnclosingMethodMustNotBeAsync) { if (enclosingMethodSymbol.IsAsync) { return(false); } if (!EnclosingMethodCanBeMadeAsync()) { return(false); } } else // Enclosing method must be async. { if (!enclosingMethodSymbol.IsAsync) { return(false); } } // We can have the following situations: // 1. someObject.SomeInstanceMethod() // 2. someObject.SomeExtensionMethod() // 3. SomeType.SomeStaticMethod() // 4. <build in type keyword>.SomeStaticMethod() // 5. SomeInstanceMethod(); // 6. SomeStaticMethod(); // 7. this.SomeInstanceMethod(); // A potential asynchronous equivalent can be defined on // the same type on which the synchronous method is defined. // But if the synchronous method is itself an extension method, // the asynchronous equivalent could be defined on the type // of the object on which the method is called. // And other way around, if the synchronous method is an instance // method, the asynchronous equivalent could be an extension method // on an arbitrary type that extends the type of the instance ;-) // Long story short, the search for the asynchronous equivalent // has to check both the containing type of the synchronous method // and all the possible methods that can be called on the instance // on which the synchronous method is called (if there is such). // Let's check the method containing type first. if (TypeContainsAsynchronousEquivalentOf(semanticModel, method.ContainingType, method, invocation)) { return(true); } // Let's now check the type on which the method is called, // if there is such. var calledOnType = GetCalledOnType(); if (calledOnType == null || calledOnType == method.ContainingType) { return(false); } if (TypeContainsAsynchronousEquivalentOf(semanticModel, calledOnType, method, invocation)) { return(true); } return(false); IMethodSymbol GetEnclosingMethod() { var enclosingMethod = invocation.FirstAncestorOrSelf <MethodDeclarationSyntax>(); if (enclosingMethod == null) { return(null); } return(semanticModel.GetDeclaredSymbol(enclosingMethod)); } bool EnclosingMethodCanBeMadeAsync() { return(EnclosingMethodDoesNotOverrideNonChangeableBaseClassMethod() && EnclosingMethodDoesNotImplementNonChangeableInterfaceMethod() && EnclosingMethodDoesNotAlreadyHaveAsynchronousEquivalent()); bool EnclosingMethodDoesNotOverrideNonChangeableBaseClassMethod() { if (!enclosingMethodSymbol.IsOverride) { return(true); } return(enclosingMethodSymbol.OverriddenMethod? .ContainingType? .Locations.All(location => location.IsInSource) == true); } bool EnclosingMethodDoesNotImplementNonChangeableInterfaceMethod() { // If the enclosing method implements an interface method // we have to see if that interface can be changed. // Since it could implement more then one interface, we have // to check of all of them can be changed. // (Changed means made async.) // If they cannot, means if they are referenced from an assembly // and not defined in code, we assume that the enclosing // method implements a non-changeable interface method. return(enclosingMethodSymbol.GetImplementedInterfaceMethods(semanticModel) .All(interfaceMethod => interfaceMethod .ContainingType?.Locations.All(location => location.IsInSource) == true)); } bool EnclosingMethodDoesNotAlreadyHaveAsynchronousEquivalent() { return(!TypeContainsAsynchronousEquivalentOf(semanticModel, enclosingMethodSymbol.ContainingType, enclosingMethodSymbol)); } } bool MethodIsInvokedWithinItsContainingType() { var invokedInType = invocation.FirstAncestorOrSelf <TypeDeclarationSyntax>(); // This should never happen. It means we have some issue in the // syntax tree. If that's the case, just cancel any further analysis // by stating that the method is called withing its containing type. if (invokedInType == null) { return(true); } if (method.ContainingType?.Equals(semanticModel.GetDeclaredSymbol(invokedInType)) == true) { return(true); } return(false); } INamedTypeSymbol GetCalledOnType() { if (!(invocation.Expression is MemberAccessExpressionSyntax memberAccess)) { return(null); } if (memberAccess.Expression == null) { return(null); } return(semanticModel.GetTypeInfo(memberAccess.Expression).Type as INamedTypeSymbol); } }
protected BaseAwaitingEquivalentAsynchronousMethod(EnclosingMethodAsyncStatus enclosingMethodAsyncStatus) { this.enclosingMethodAsyncStatus = enclosingMethodAsyncStatus; }