File: src\RoslynAnalyzers\Utilities\Compiler\Extensions\IMethodSymbolExtensions.cs
Web Access
Project: src\src\RoslynAnalyzers\Roslyn.Diagnostics.Analyzers\Core\Roslyn.Diagnostics.Analyzers.csproj (Roslyn.Diagnostics.Analyzers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable warnings
 
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
 
#if HAS_IOPERATION
using System.Threading;
using Microsoft.CodeAnalysis.Operations;
#endif
 
namespace Analyzer.Utilities.Extensions
{
    internal static class IMethodSymbolExtensions
    {
        /// <summary>
        /// Checks if the given method overrides <see cref="object.Equals(object)"/>.
        /// </summary>
        public static bool IsObjectEqualsOverride(this IMethodSymbol method)
        {
            return method != null &&
                method.IsOverride &&
                method.Name == WellKnownMemberNames.ObjectEquals &&
                method.ReturnType.SpecialType == SpecialType.System_Boolean &&
                method.Parameters.Length == 1 &&
                method.Parameters[0].Type.SpecialType == SpecialType.System_Object &&
                IsObjectMethodOverride(method);
        }
 
        /// <summary>
        /// Checks if the given method is <see cref="object.Equals(object)"/>.
        /// </summary>
        public static bool IsObjectEquals(this IMethodSymbol method)
        {
            return method != null &&
                method.ContainingType.SpecialType == SpecialType.System_Object &&
                method.IsVirtual &&
                method.Name == WellKnownMemberNames.ObjectEquals &&
                method.ReturnType.SpecialType == SpecialType.System_Boolean &&
                method.Parameters.Length == 1 &&
                method.Parameters[0].Type.SpecialType == SpecialType.System_Object;
        }
 
        /// <summary>
        /// Checks if the given <paramref name="method"/> is <see cref="object.Equals(object, object)"/> or <see cref="object.ReferenceEquals(object, object)"/>.
        /// </summary>
        public static bool IsStaticObjectEqualsOrReferenceEquals(this IMethodSymbol method)
        {
            return method != null &&
                method.IsStatic &&
                method.ContainingType.SpecialType == SpecialType.System_Object &&
                method.Parameters.Length == 2 &&
                method.ReturnType.SpecialType == SpecialType.System_Boolean &&
                method.Parameters[0].Type.SpecialType == SpecialType.System_Object &&
                method.Parameters[1].Type.SpecialType == SpecialType.System_Object &&
                (method.Name == WellKnownMemberNames.ObjectEquals || method.Name == "ReferenceEquals");
        }
 
        /// <summary>
        /// Checks if the given method overrides Object.GetHashCode.
        /// </summary>
        public static bool IsGetHashCodeOverride(this IMethodSymbol method)
        {
            return method != null &&
                   method.IsOverride &&
                   method.Name == WellKnownMemberNames.ObjectGetHashCode &&
                   method.ReturnType.SpecialType == SpecialType.System_Int32 &&
                   method.Parameters.IsEmpty &&
                   IsObjectMethodOverride(method);
        }
 
        /// <summary>
        /// Checks if the given method overrides Object.ToString.
        /// </summary>
        public static bool IsToStringOverride(this IMethodSymbol method)
        {
            return method != null &&
                   method.IsOverride &&
                   method.ReturnType.SpecialType == SpecialType.System_String &&
                   method.Name == WellKnownMemberNames.ObjectToString &&
                   method.Parameters.IsEmpty &&
                   IsObjectMethodOverride(method);
        }
 
        /// <summary>
        /// Checks if the given method overrides a method from System.Object
        /// </summary>
        private static bool IsObjectMethodOverride(IMethodSymbol method)
        {
            IMethodSymbol overriddenMethod = method.OverriddenMethod;
            while (overriddenMethod != null)
            {
                if (overriddenMethod.ContainingType.SpecialType == SpecialType.System_Object)
                {
                    return true;
                }
 
                overriddenMethod = overriddenMethod.OverriddenMethod;
            }
 
            return false;
        }
 
        /// <summary>
        /// Checks if the given method is a Finalizer implementation.
        /// </summary>
        public static bool IsFinalizer(this IMethodSymbol method)
        {
            if (method.MethodKind == MethodKind.Destructor)
            {
                return true; // for C#
            }
 
            if (method.Name != WellKnownMemberNames.DestructorName || !method.Parameters.IsEmpty || !method.ReturnsVoid)
            {
                return false;
            }
 
            IMethodSymbol overridden = method.OverriddenMethod;
 
            if (method.ContainingType.SpecialType == SpecialType.System_Object)
            {
                // This is object.Finalize
                return true;
            }
 
            if (overridden == null)
            {
                return false;
            }
 
            for (IMethodSymbol o = overridden.OverriddenMethod; o != null; o = o.OverriddenMethod)
            {
                overridden = o;
            }
 
            return overridden.ContainingType.SpecialType == SpecialType.System_Object; // it is object.Finalize
        }
 
        /// <summary>
        /// Checks if the given method is an implementation of the given interface method
        /// Substituted with the given typeargument.
        /// </summary>
        public static bool IsImplementationOfInterfaceMethod(this IMethodSymbol method, ITypeSymbol? typeArgument, [NotNullWhen(returnValue: true)] INamedTypeSymbol? interfaceType, string interfaceMethodName)
        {
            INamedTypeSymbol? constructedInterface = typeArgument != null ? interfaceType?.Construct(typeArgument) : interfaceType;
 
            return constructedInterface?.GetMembers(interfaceMethodName).FirstOrDefault() is IMethodSymbol interfaceMethod &&
                SymbolEqualityComparer.Default.Equals(method, method.ContainingType.FindImplementationForInterfaceMember(interfaceMethod));
        }
 
        /// <summary>
        /// Checks if the given method implements IDisposable.Dispose()
        /// </summary>
        public static bool IsDisposeImplementation(this IMethodSymbol method, Compilation compilation)
        {
            INamedTypeSymbol? iDisposable = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIDisposable);
            return method.IsDisposeImplementation(iDisposable);
        }
 
        /// <summary>
        /// Checks if the given method implements IAsyncDisposable.Dispose()
        /// </summary>
        public static bool IsAsyncDisposeImplementation(this IMethodSymbol method, Compilation compilation)
        {
            INamedTypeSymbol? iAsyncDisposable = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIAsyncDisposable);
            INamedTypeSymbol? valueTaskType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksValueTask);
            return method.IsAsyncDisposeImplementation(iAsyncDisposable, valueTaskType);
        }
 
        /// <summary>
        /// Checks if the given method implements <see cref="IDisposable.Dispose"/> or overrides an implementation of <see cref="IDisposable.Dispose"/>.
        /// </summary>
        public static bool IsDisposeImplementation([NotNullWhen(returnValue: true)] this IMethodSymbol? method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? iDisposable)
        {
            if (method == null)
            {
                return false;
            }
 
            if (method.IsOverride)
            {
                return method.OverriddenMethod.IsDisposeImplementation(iDisposable);
            }
 
            // Identify the implementor of IDisposable.Dispose in the given method's containing type and check
            // if it is the given method.
            return method.ReturnsVoid &&
                method.Parameters.IsEmpty &&
                method.IsImplementationOfInterfaceMethod(null, iDisposable, "Dispose");
        }
 
        /// <summary>
        /// Checks if the given method implements "IAsyncDisposable.Dispose" or overrides an implementation of "IAsyncDisposable.Dispose".
        /// </summary>
        public static bool IsAsyncDisposeImplementation([NotNullWhen(returnValue: true)] this IMethodSymbol? method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? iAsyncDisposable, [NotNullWhen(returnValue: true)] INamedTypeSymbol? valueTaskType)
        {
            if (method == null)
            {
                return false;
            }
 
            if (method.IsOverride)
            {
                return method.OverriddenMethod.IsAsyncDisposeImplementation(iAsyncDisposable, valueTaskType);
            }
 
            // Identify the implementor of IAsyncDisposable.Dispose in the given method's containing type and check
            // if it is the given method.
            return SymbolEqualityComparer.Default.Equals(method.ReturnType, valueTaskType) &&
                method.Parameters.IsEmpty &&
                method.IsImplementationOfInterfaceMethod(null, iAsyncDisposable, "DisposeAsync");
        }
 
        /// <summary>
        /// Checks if the given method has the signature "void Dispose()".
        /// </summary>
        private static bool HasDisposeMethodSignature(this IMethodSymbol method)
        {
            return method.Name == "Dispose" && method.MethodKind == MethodKind.Ordinary &&
                method.ReturnsVoid && method.Parameters.IsEmpty;
        }
 
        /// <summary>
        /// Checks if the given method matches Dispose method convention and can be recognized by "using".
        /// </summary>
        public static bool HasDisposeSignatureByConvention(this IMethodSymbol method)
        {
            return method.HasDisposeMethodSignature()
                && !method.IsStatic
                && !method.IsPrivate();
        }
 
        /// <summary>
        /// Checks if the given method has the signature "void Dispose(bool)".
        /// </summary>
        public static bool HasDisposeBoolMethodSignature(this IMethodSymbol method)
        {
            if (method.Name == "Dispose" && method.MethodKind == MethodKind.Ordinary &&
                method.ReturnsVoid && method.Parameters.Length == 1)
            {
                IParameterSymbol parameter = method.Parameters[0];
                return parameter.Type != null &&
                    parameter.Type.SpecialType == SpecialType.System_Boolean &&
                    parameter.RefKind == RefKind.None;
            }
 
            return false;
        }
 
        /// <summary>
        /// Checks if the given method has the signature "void Close()".
        /// </summary>
        private static bool HasDisposeCloseMethodSignature(this IMethodSymbol method)
        {
            return method.Name == "Close" && method.MethodKind == MethodKind.Ordinary &&
                method.ReturnsVoid && method.Parameters.IsEmpty;
        }
 
        /// <summary>
        /// Checks if the given method has the signature "Task CloseAsync()".
        /// </summary>
        private static bool HasDisposeCloseAsyncMethodSignature(this IMethodSymbol method, INamedTypeSymbol? taskType)
            => taskType != null && method.Parameters.IsEmpty && method.Name == "CloseAsync" &&
                SymbolEqualityComparer.Default.Equals(method.ReturnType, taskType);
 
        /// <summary>
        /// Checks if the given method has the signature "Task DisposeAsync()" or "ValueTask DisposeAsync()" or "ConfiguredValueTaskAwaitable DisposeAsync()".
        /// </summary>
        private static bool HasDisposeAsyncMethodSignature(this IMethodSymbol method,
            INamedTypeSymbol? task,
            INamedTypeSymbol? valueTask,
            INamedTypeSymbol? configuredValueTaskAwaitable)
        {
            return method.Name == "DisposeAsync" &&
                method.MethodKind == MethodKind.Ordinary &&
                method.Parameters.IsEmpty &&
                (SymbolEqualityComparer.Default.Equals(method.ReturnType, task) ||
                 SymbolEqualityComparer.Default.Equals(method.ReturnType, valueTask) ||
                 SymbolEqualityComparer.Default.Equals(method.ReturnType, configuredValueTaskAwaitable));
        }
 
        /// <summary>
        /// Checks if the given method has the signature "override Task DisposeCoreAsync(bool)" or "override Task DisposeAsyncCore(bool)".
        /// </summary>
        private static bool HasOverriddenDisposeCoreAsyncMethodSignature(this IMethodSymbol method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? task)
        {
            return (method.Name == "DisposeAsyncCore" || method.Name == "DisposeCoreAsync") &&
                method.MethodKind == MethodKind.Ordinary &&
                method.IsOverride &&
                SymbolEqualityComparer.Default.Equals(method.ReturnType, task) &&
                method.Parameters.Length == 1 &&
                method.Parameters[0].Type.SpecialType == SpecialType.System_Boolean;
        }
 
        /// <summary>
        /// Checks if the given method has the signature "{virtual|override} ValueTask DisposeCoreAsync()" or "{virtual|override} ValueTask DisposeAsyncCore()".
        /// </summary>
        private static bool HasVirtualOrOverrideDisposeCoreAsyncMethodSignature(this IMethodSymbol method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? valueTask)
        {
            return (method.Name == "DisposeAsyncCore" || method.Name == "DisposeCoreAsync") &&
                method.MethodKind == MethodKind.Ordinary &&
                (method.IsVirtual || method.IsOverride) &&
                SymbolEqualityComparer.Default.Equals(method.ReturnType, valueTask) &&
                method.Parameters.Length == 0;
        }
 
        /// <summary>
        /// Gets the <see cref="DisposeMethodKind"/> for the given method.
        /// </summary>
        public static DisposeMethodKind GetDisposeMethodKind(this IMethodSymbol method, Compilation compilation)
        {
            INamedTypeSymbol? iDisposable = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIDisposable);
            INamedTypeSymbol? iAsyncDisposable = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIAsyncDisposable);
            INamedTypeSymbol? configuredAsyncDisposable = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesConfiguredAsyncDisposable);
            INamedTypeSymbol? task = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask);
            INamedTypeSymbol? valueTask = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksValueTask);
            INamedTypeSymbol? configuredValueTaskAwaitable = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesConfiguredValueTaskAwaitable);
            return method.GetDisposeMethodKind(iDisposable, iAsyncDisposable, configuredAsyncDisposable, task, valueTask, configuredValueTaskAwaitable);
        }
 
        /// <summary>
        /// Gets the <see cref="DisposeMethodKind"/> for the given method.
        /// </summary>
        public static DisposeMethodKind GetDisposeMethodKind(
            this IMethodSymbol method,
            INamedTypeSymbol? iDisposable,
            INamedTypeSymbol? iAsyncDisposable,
            INamedTypeSymbol? configuredAsyncDisposable,
            INamedTypeSymbol? task,
            INamedTypeSymbol? valueTask,
            INamedTypeSymbol? configuredValueTaskAwaitable)
        {
            if (method.ContainingType.IsDisposable(iDisposable, iAsyncDisposable, configuredAsyncDisposable))
            {
                if (IsDisposeImplementation(method, iDisposable) ||
                    (SymbolEqualityComparer.Default.Equals(method.ContainingType, iDisposable) &&
                     method.HasDisposeMethodSignature())
#if CODEANALYSIS_V3_OR_BETTER
                    || (method.ContainingType.IsRefLikeType &&
                     method.HasDisposeSignatureByConvention())
#endif
                )
                {
                    return DisposeMethodKind.Dispose;
                }
                else if (method.HasDisposeBoolMethodSignature())
                {
                    return DisposeMethodKind.DisposeBool;
                }
                else if (method.IsAsyncDisposeImplementation(iAsyncDisposable, valueTask) || method.HasDisposeAsyncMethodSignature(task, valueTask, configuredValueTaskAwaitable))
                {
                    return DisposeMethodKind.DisposeAsync;
                }
                else if (method.HasOverriddenDisposeCoreAsyncMethodSignature(task))
                {
                    return DisposeMethodKind.DisposeCoreAsync;
                }
                else if (method.HasVirtualOrOverrideDisposeCoreAsyncMethodSignature(valueTask))
                {
                    return DisposeMethodKind.DisposeCoreAsync;
                }
                else if (method.HasDisposeCloseMethodSignature())
                {
                    return DisposeMethodKind.Close;
                }
                else if (method.HasDisposeCloseAsyncMethodSignature(task))
                {
                    return DisposeMethodKind.CloseAsync;
                }
            }
 
            return DisposeMethodKind.None;
        }
 
        /// <summary>
        /// Checks if the given method implements 'System.Runtime.Serialization.IDeserializationCallback.OnDeserialization' or overrides an implementation of 'System.Runtime.Serialization.IDeserializationCallback.OnDeserialization'/>.
        /// </summary>
        public static bool IsOnDeserializationImplementation([NotNullWhen(returnValue: true)] this IMethodSymbol? method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? iDeserializationCallback)
        {
            if (method == null)
            {
                return false;
            }
 
            if (method.IsOverride)
            {
                return method.OverriddenMethod.IsOnDeserializationImplementation(iDeserializationCallback);
            }
 
            // Identify the implementor of IDisposable.Dispose in the given method's containing type and check
            // if it is the given method.
            return method.ReturnsVoid &&
                method.Parameters.Length == 1 &&
                method.Parameters[0].Type.SpecialType == SpecialType.System_Object &&
                method.IsImplementationOfInterfaceMethod(null, iDeserializationCallback, "OnDeserialization");
        }
 
        public static bool IsSerializationConstructor([NotNullWhen(returnValue: true)] this IMethodSymbol? method, INamedTypeSymbol? serializationInfoType, INamedTypeSymbol? streamingContextType)
            => method.IsConstructor() &&
                method.Parameters.Length == 2 &&
                SymbolEqualityComparer.Default.Equals(method.Parameters[0].Type, serializationInfoType) &&
                SymbolEqualityComparer.Default.Equals(method.Parameters[1].Type, streamingContextType);
 
        public static bool IsGetObjectData([NotNullWhen(returnValue: true)] this IMethodSymbol? method, INamedTypeSymbol? serializationInfoType, INamedTypeSymbol? streamingContextType)
            => method?.Name == "GetObjectData" &&
                method.ReturnsVoid &&
                method.Parameters.Length == 2 &&
                SymbolEqualityComparer.Default.Equals(method.Parameters[0].Type, serializationInfoType) &&
                SymbolEqualityComparer.Default.Equals(method.Parameters[1].Type, streamingContextType);
 
        /// <summary>
        /// Checks if the method is a property getter.
        /// </summary>
        public static bool IsPropertyGetter(this IMethodSymbol method)
        {
            return method.MethodKind == MethodKind.PropertyGet &&
                   method.AssociatedSymbol?.GetParameters().Length == 0;
        }
 
        /// <summary>
        /// Checks if the method is the getter for an indexer.
        /// </summary>
        public static bool IsIndexerGetter(this IMethodSymbol method)
        {
            return method.MethodKind == MethodKind.PropertyGet &&
                   method.AssociatedSymbol.IsIndexer();
        }
 
        /// <summary>
        /// Checks if the method is an accessor for a property.
        /// </summary>
        public static bool IsPropertyAccessor(this IMethodSymbol method)
        {
            return method.MethodKind is MethodKind.PropertyGet or
                   MethodKind.PropertySet;
        }
 
        /// <summary>
        /// Checks if the method is an accessor for an event.
        /// </summary>
        public static bool IsEventAccessor(this IMethodSymbol method)
        {
            return method.MethodKind is MethodKind.EventAdd or
                   MethodKind.EventRaise or
                   MethodKind.EventRemove;
        }
 
        public static bool IsOperator(this IMethodSymbol methodSymbol)
        {
            return methodSymbol.MethodKind is MethodKind.UserDefinedOperator or MethodKind.BuiltinOperator;
        }
 
        public static bool HasOptionalParameters(this IMethodSymbol methodSymbol)
        {
            return methodSymbol.Parameters.Any(p => p.IsOptional);
        }
 
        public static IEnumerable<IMethodSymbol> GetOverloads(this IMethodSymbol? method)
        {
            var methods = method?.ContainingType?.GetMembers(method.Name).OfType<IMethodSymbol>();
            if (methods != null)
            {
                foreach (var member in methods)
                {
                    if (!SymbolEqualityComparer.Default.Equals(member, method))
                    {
                        yield return member;
                    }
                }
            }
        }
 
        /// <summary>
        /// Set of well-known collection add method names.
        /// Used in <see cref="IsCollectionAddMethod"/> heuristic.
        /// </summary>
        private static readonly ImmutableHashSet<string> s_collectionAddMethodNameVariants =
            ImmutableHashSet.Create(StringComparer.Ordinal, "Add", "AddOrUpdate", "GetOrAdd", "TryAdd", "TryUpdate");
 
        /// <summary>
        /// Determine if the specific method is an Add method that adds to a collection.
        /// </summary>
        /// <param name="method">The method to test.</param>
        /// <param name="iCollectionTypes">Collection types.</param>
        /// <returns>'true' if <paramref name="method"/> is believed to be the add method of a collection.</returns>
        /// <remarks>
        /// We use the following heuristic to determine if a method is a collection add method:
        /// 1. Method's enclosing type implements any of the given <paramref name="iCollectionTypes"/>.
        /// 2. Any of the following name heuristics are met:
        ///     a. Method's name is from one of the well-known add method names from <see cref="s_collectionAddMethodNameVariants"/> ("Add", "AddOrUpdate", "GetOrAdd", "TryAdd", or "TryUpdate")
        ///     b. Method's name begins with "Add" (FxCop compat)
        /// </remarks>
        public static bool IsCollectionAddMethod(this IMethodSymbol method, ImmutableHashSet<INamedTypeSymbol> iCollectionTypes)
        {
            if (iCollectionTypes.IsEmpty)
            {
                return false;
            }
 
            if (!s_collectionAddMethodNameVariants.Contains(method.Name) &&
                !method.Name.StartsWith("Add", StringComparison.Ordinal))
            {
                return false;
            }
 
            return method.ContainingType.AllInterfaces.Any(i => iCollectionTypes.Contains(i.OriginalDefinition));
        }
 
        /// <summary>
        /// Determine if the specific method is a Task.FromResult method that wraps a result in a task.
        /// </summary>
        /// <param name="method">The method to test.</param>
        /// <param name="taskType">Task type.</param>
        public static bool IsTaskFromResultMethod(this IMethodSymbol method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? taskType)
            => method.Name.Equals("FromResult", StringComparison.Ordinal) &&
               SymbolEqualityComparer.Default.Equals(method.ContainingType, taskType);
 
        /// <summary>
        /// Determine if the specific method is a Task.ConfigureAwait(bool) method.
        /// </summary>
        /// <param name="method">The method to test.</param>
        /// <param name="genericTaskType">Generic task type.</param>
        public static bool IsTaskConfigureAwaitMethod(this IMethodSymbol method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? genericTaskType)
            => method.Name.Equals("ConfigureAwait", StringComparison.Ordinal) &&
               method.Parameters.Length == 1 &&
               method.Parameters[0].Type.SpecialType == SpecialType.System_Boolean &&
               SymbolEqualityComparer.Default.Equals(method.ContainingType.OriginalDefinition, genericTaskType);
 
        /// <summary>
        /// Determine if the specific method is a TaskAsyncEnumerableExtensions.ConfigureAwait(this IAsyncDisposable, bool) extension method.
        /// </summary>
        /// <param name="method">The method to test.</param>
        /// <param name="asyncDisposableType">IAsyncDisposable named type.</param>
        /// <param name="taskAsyncEnumerableExtensions">System.Threading.Tasks.TaskAsyncEnumerableExtensions named type.</param>
        public static bool IsAsyncDisposableConfigureAwaitMethod(this IMethodSymbol method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? asyncDisposableType, [NotNullWhen(returnValue: true)] INamedTypeSymbol? taskAsyncEnumerableExtensions)
            => method.IsExtensionMethod &&
               method.Name.Equals("ConfigureAwait", StringComparison.Ordinal) &&
               method.Parameters.Length == 2 &&
               SymbolEqualityComparer.Default.Equals(method.Parameters[0].Type, asyncDisposableType) &&
               method.Parameters[1].Type.SpecialType == SpecialType.System_Boolean &&
               SymbolEqualityComparer.Default.Equals(method.ContainingType.OriginalDefinition, taskAsyncEnumerableExtensions) &&
               taskAsyncEnumerableExtensions.IsStatic;
 
#if HAS_IOPERATION
        /// <summary>
        /// PERF: Cache from method symbols to their topmost block operations to enable interprocedural flow analysis
        /// across analyzers and analyzer callbacks to re-use the operations, semanticModel and control flow graph.
        /// </summary>
        /// <remarks>Also see <see cref="IOperationExtensions.s_operationToCfgCache"/></remarks>
        private static readonly BoundedCache<Compilation, ConcurrentDictionary<IMethodSymbol, IBlockOperation?>> s_methodToTopmostOperationBlockCache
            = new();
 
        /// <summary>
        /// Returns the topmost <see cref="IBlockOperation"/> for given <paramref name="method"/>.
        /// </summary>
        public static IBlockOperation? GetTopmostOperationBlock(this IMethodSymbol method, Compilation compilation, CancellationToken cancellationToken = default)
        {
            var methodToBlockMap = s_methodToTopmostOperationBlockCache.GetOrCreateValue(compilation);
            return methodToBlockMap.GetOrAdd(method, ComputeTopmostOperationBlock);
 
            // Local functions.
            IBlockOperation? ComputeTopmostOperationBlock(IMethodSymbol unused)
            {
                if (!SymbolEqualityComparer.Default.Equals(method.ContainingAssembly, compilation.Assembly))
                {
                    return null;
                }
 
                foreach (var decl in method.DeclaringSyntaxReferences)
                {
                    var syntax = decl.GetSyntax(cancellationToken);
 
                    // VB Workaround: declaration.GetSyntax returns StatementSyntax nodes instead of BlockSyntax nodes
                    //                GetOperation returns null for StatementSyntax, and the method's operation block for BlockSyntax.
                    if (compilation.Language == LanguageNames.VisualBasic)
                    {
                        syntax = syntax.Parent;
                    }
 
                    var semanticModel = compilation.GetSemanticModel(syntax.SyntaxTree);
                    foreach (var descendant in syntax.DescendantNodesAndSelf())
                    {
                        var operation = semanticModel.GetOperation(descendant, cancellationToken);
                        if (operation is IBlockOperation blockOperation)
                        {
                            return blockOperation;
                        }
                    }
                }
 
                return null;
            }
        }
#endif
 
        public static bool IsLambdaOrLocalFunctionOrDelegate(this IMethodSymbol method)
        {
            return method.MethodKind switch
            {
                MethodKind.LambdaMethod or MethodKindEx.LocalFunction or MethodKind.DelegateInvoke => true,
                _ => false,
            };
        }
 
        public static bool IsLambdaOrLocalFunction(this IMethodSymbol method)
        {
            return method.MethodKind switch
            {
                MethodKind.LambdaMethod or MethodKindEx.LocalFunction => true,
                _ => false,
            };
        }
 
        public static int GetParameterIndex(this IMethodSymbol methodSymbol, IParameterSymbol parameterSymbol)
        {
            for (var i = 0; i < methodSymbol.Parameters.Length; i++)
            {
                if (SymbolEqualityComparer.Default.Equals(parameterSymbol, methodSymbol.Parameters[i]))
                {
                    return i;
                }
            }
 
            throw new ArgumentException("Invalid parameter", nameof(parameterSymbol));
        }
 
        /// <summary>
        /// Returns true for void returning methods with two parameters, where
        /// the first parameter is of <see cref="object"/> type and the second
        /// parameter inherits from or equals <see cref="EventArgs"/> type or
        /// whose name ends with 'EventArgs'.
        /// </summary>
        public static bool HasEventHandlerSignature(this IMethodSymbol method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? eventArgsType)
            => eventArgsType != null &&
               method.ReturnsVoid &&
               method.Parameters.Length == 2 &&
               method.Parameters[0].Type.SpecialType == SpecialType.System_Object &&
               // FxCop compat: Struct with name ending with "EventArgs" are allowed
               // + UWP has specific EventArgs not inheriting from 'System.EventArgs'.
               // See https://github.com/dotnet/roslyn-analyzers/issues/3106
               (method.Parameters[1].Type.DerivesFrom(eventArgsType, baseTypesOnly: true) || method.Parameters[1].Type.Name.EndsWith("EventArgs", StringComparison.Ordinal));
 
        public static bool IsLockMethod(this IMethodSymbol method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? systemThreadingMonitor)
        {
            // "System.Threading.Monitor.Enter(object)" OR "System.Threading.Monitor.Enter(object, bool)"
            return method.Name == "Enter" &&
                   SymbolEqualityComparer.Default.Equals(method.ContainingType, systemThreadingMonitor) &&
                   method.ReturnsVoid &&
                   !method.Parameters.IsEmpty &&
                   method.Parameters[0].Type.SpecialType == SpecialType.System_Object;
        }
 
        public static bool IsInterlockedExchangeMethod(this IMethodSymbol method, INamedTypeSymbol? systemThreadingInterlocked)
        {
            Debug.Assert(SymbolEqualityComparer.Default.Equals(method.ContainingType.OriginalDefinition, systemThreadingInterlocked));
 
            // "System.Threading.Interlocked.Exchange(ref T, T)"
            return method.Name == "Exchange" &&
                   method.Parameters.Length == 2 &&
                   method.Parameters[0].RefKind == RefKind.Ref &&
                   method.Parameters[1].RefKind != RefKind.Ref &&
                   SymbolEqualityComparer.Default.Equals(method.Parameters[0].Type, method.Parameters[1].Type);
        }
 
        public static bool IsInterlockedCompareExchangeMethod(this IMethodSymbol method, INamedTypeSymbol? systemThreadingInterlocked)
        {
            Debug.Assert(SymbolEqualityComparer.Default.Equals(method.ContainingType.OriginalDefinition, systemThreadingInterlocked));
 
            // "System.Threading.Interlocked.CompareExchange(ref T, T, T)"
            return method.Name == "CompareExchange" &&
                   method.Parameters.Length == 3 &&
                   method.Parameters[0].RefKind == RefKind.Ref &&
                   method.Parameters[1].RefKind != RefKind.Ref &&
                   method.Parameters[2].RefKind != RefKind.Ref &&
                   SymbolEqualityComparer.Default.Equals(method.Parameters[0].Type, method.Parameters[1].Type) &&
                   SymbolEqualityComparer.Default.Equals(method.Parameters[1].Type, method.Parameters[2].Type);
        }
 
        public static bool HasParameterWithDelegateType(this IMethodSymbol methodSymbol)
            => methodSymbol.Parameters.Any(p => p.Type.TypeKind == TypeKind.Delegate);
 
        /// <summary>
        /// Find out if the method overrides from target virtual method of a certain type
        /// or it is the virtual method itself.
        /// </summary>
        /// <param name="methodSymbol">The method</param>
        /// <param name="typeSymbol">The type has virtual method</param>
        public static bool IsOverrideOrVirtualMethodOf([NotNullWhen(returnValue: true)] this IMethodSymbol? methodSymbol, [NotNullWhen(returnValue: true)] INamedTypeSymbol? typeSymbol)
        {
            if (methodSymbol == null)
            {
                return false;
            }
            else
            {
                if (SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, typeSymbol))
                {
                    return true;
                }
                else
                {
                    return IsOverrideOrVirtualMethodOf(methodSymbol.OverriddenMethod, typeSymbol);
                }
            }
        }
 
        /// <summary>
        /// Returns true if this is a bool returning static method whose name starts with "IsNull"
        /// with a single parameter whose type is not a value type.
        /// For example, "static bool string.IsNullOrEmpty()"
        /// </summary>
        public static bool IsArgumentNullCheckMethod(this IMethodSymbol method)
        {
            return method.IsStatic &&
                method.ReturnType.SpecialType == SpecialType.System_Boolean &&
                method.Name.StartsWith("IsNull", StringComparison.Ordinal) &&
                method.Parameters.Length == 1 &&
                !method.Parameters[0].Type.IsValueType;
        }
 
        public static bool IsBenchmarkOrXUnitTestMethod(this IMethodSymbol method, ConcurrentDictionary<INamedTypeSymbol, bool> knownTestAttributes, INamedTypeSymbol? benchmarkAttribute, INamedTypeSymbol? xunitFactAttribute)
        {
            foreach (var attribute in method.GetAttributes())
            {
                if (attribute.AttributeClass.IsBenchmarkOrXUnitTestAttribute(knownTestAttributes, benchmarkAttribute, xunitFactAttribute))
                    return true;
            }
 
            return false;
        }
 
        /// <summary>
        /// Check if a method is an auto-property accessor.
        /// </summary>
        public static bool IsAutoPropertyAccessor(this IMethodSymbol methodSymbol)
            => methodSymbol.IsPropertyAccessor()
            && methodSymbol.AssociatedSymbol is IPropertySymbol propertySymbol
            && propertySymbol.IsAutoProperty();
 
        /// <summary>
        /// Check if the given <paramref name="methodSymbol"/> is an implicitly generated method for top level statements.
        /// </summary>
        public static bool IsTopLevelStatementsEntryPointMethod([NotNullWhen(true)] this IMethodSymbol? methodSymbol)
            => methodSymbol?.IsStatic == true && methodSymbol.Name switch
            {
                "$Main" => true,
                "<Main>$" => true,
                _ => false
            };
 
        public static bool IsGetAwaiterFromAwaitablePattern([NotNullWhen(true)] this IMethodSymbol? method,
            [NotNullWhen(true)] INamedTypeSymbol? inotifyCompletionType,
            [NotNullWhen(true)] INamedTypeSymbol? icriticalNotifyCompletionType)
        {
            if (method is null
                || !method.Name.Equals("GetAwaiter", StringComparison.Ordinal)
                || method.Parameters.Length != 0)
            {
                return false;
            }
 
            var returnType = method.ReturnType?.OriginalDefinition;
            if (returnType is null)
            {
                return false;
            }
 
            return returnType.DerivesFrom(inotifyCompletionType) ||
                returnType.DerivesFrom(icriticalNotifyCompletionType);
        }
 
        public static bool IsGetResultFromAwaiterPattern(
            [NotNullWhen(true)] this IMethodSymbol? method,
            [NotNullWhen(true)] INamedTypeSymbol? inotifyCompletionType,
            [NotNullWhen(true)] INamedTypeSymbol? icriticalNotifyCompletionType)
        {
            if (method is null
                || !method.Name.Equals("GetResult", StringComparison.Ordinal)
                || method.Parameters.Length != 0)
            {
                return false;
            }
 
            var containingType = method.ContainingType?.OriginalDefinition;
            if (containingType is null)
            {
                return false;
            }
 
            return containingType.DerivesFrom(inotifyCompletionType) ||
                containingType.DerivesFrom(icriticalNotifyCompletionType);
        }
 
        public static ImmutableArray<IMethodSymbol> GetOriginalDefinitions(this IMethodSymbol methodSymbol)
        {
            ImmutableArray<IMethodSymbol>.Builder originalDefinitionsBuilder = ImmutableArray.CreateBuilder<IMethodSymbol>();
 
            if (methodSymbol.IsOverride && (methodSymbol.OverriddenMethod != null))
            {
                originalDefinitionsBuilder.Add(methodSymbol.OverriddenMethod);
            }
 
            if (!methodSymbol.ExplicitInterfaceImplementations.IsEmpty)
            {
                originalDefinitionsBuilder.AddRange(methodSymbol.ExplicitInterfaceImplementations);
            }
 
            var typeSymbol = methodSymbol.ContainingType;
            var methodSymbolName = methodSymbol.Name;
 
            originalDefinitionsBuilder.AddRange(typeSymbol.AllInterfaces
                .SelectMany(m => m.GetMembers(methodSymbolName))
                .OfType<IMethodSymbol>()
                .Where(m => methodSymbol.Parameters.Length == m.Parameters.Length
                            && methodSymbol.Arity == m.Arity
                            && typeSymbol.FindImplementationForInterfaceMember(m) != null));
 
            return originalDefinitionsBuilder.ToImmutable();
        }
    }
}