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