|
// 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.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Shared.Extensions;
internal static partial class ISymbolExtensions
{
public static string ToNameDisplayString(this ISymbol symbol)
=> symbol.ToDisplayString(SymbolDisplayFormats.NameFormat);
public static string ToSignatureDisplayString(this ISymbol symbol)
=> symbol.ToDisplayString(SymbolDisplayFormats.SignatureFormat);
public static bool HasPublicResultantVisibility(this ISymbol symbol)
=> symbol.GetResultantVisibility() == SymbolVisibility.Public;
public static SymbolVisibility GetResultantVisibility(this ISymbol symbol)
{
// Start by assuming it's visible.
var visibility = SymbolVisibility.Public;
switch (symbol.Kind)
{
case SymbolKind.Alias:
// Aliases are uber private. They're only visible in the same file that they
// were declared in.
return SymbolVisibility.Private;
case SymbolKind.Parameter:
// Parameters are only as visible as their containing symbol
return GetResultantVisibility(symbol.ContainingSymbol);
case SymbolKind.TypeParameter:
// Type Parameters are private.
return SymbolVisibility.Private;
}
while (symbol != null && symbol.Kind != SymbolKind.Namespace)
{
switch (symbol.DeclaredAccessibility)
{
// If we see anything private, then the symbol is private.
case Accessibility.NotApplicable:
case Accessibility.Private:
return SymbolVisibility.Private;
// If we see anything internal, then knock it down from public to
// internal.
case Accessibility.Internal:
case Accessibility.ProtectedAndInternal:
visibility = SymbolVisibility.Internal;
break;
// For anything else (Public, Protected, ProtectedOrInternal), the
// symbol stays at the level we've gotten so far.
}
symbol = symbol.ContainingSymbol;
}
return visibility;
}
public static ISymbol? GetOverriddenMember(this ISymbol? symbol, bool allowLooseMatch = false)
{
if (symbol is null)
return null;
ISymbol? exactMatch = symbol switch
{
IMethodSymbol method => method.OverriddenMethod,
IPropertySymbol property => property.OverriddenProperty,
IEventSymbol @event => @event.OverriddenEvent,
_ => null,
};
if (exactMatch != null)
return exactMatch;
if (allowLooseMatch &&
(symbol.IsVirtual || symbol.IsAbstract || symbol.IsOverride))
{
foreach (var baseType in symbol.ContainingType.GetBaseTypes())
{
if (TryFindLooseMatch(symbol, baseType, out var looseMatch))
return looseMatch;
}
}
return null;
bool TryFindLooseMatch(ISymbol symbol, INamedTypeSymbol baseType, [NotNullWhen(true)] out ISymbol? looseMatch)
{
IMethodSymbol? bestMethod = null;
var parameterCount = symbol.GetParameters().Length;
foreach (var member in baseType.GetMembers(symbol.Name))
{
if (member.Kind != symbol.Kind)
continue;
if (!member.IsOverridable())
continue;
if (symbol.Kind is SymbolKind.Event or SymbolKind.Property)
{
// We've found a matching event/property in the base type (perhaps differing by return type). This
// is a good enough match to return as a loose match for the starting symbol.
looseMatch = member;
return true;
}
else if (member is IMethodSymbol method)
{
// Prefer methods that are closed in parameter count to the original method we started with.
if (bestMethod is null || Math.Abs(method.Parameters.Length - parameterCount) < Math.Abs(bestMethod.Parameters.Length - parameterCount))
bestMethod = method;
}
}
looseMatch = bestMethod;
return looseMatch != null;
}
}
public static ImmutableArray<ISymbol> ExplicitInterfaceImplementations(this ISymbol symbol)
=> symbol switch
{
IEventSymbol @event => ImmutableArray<ISymbol>.CastUp(@event.ExplicitInterfaceImplementations),
IMethodSymbol method => ImmutableArray<ISymbol>.CastUp(method.ExplicitInterfaceImplementations),
IPropertySymbol property => ImmutableArray<ISymbol>.CastUp(property.ExplicitInterfaceImplementations),
_ => [],
};
public static ImmutableArray<ISymbol> ExplicitOrImplicitInterfaceImplementations(this ISymbol symbol)
{
if (symbol.Kind is not SymbolKind.Method and not SymbolKind.Property and not SymbolKind.Event)
return [];
var containingType = symbol.ContainingType;
var query = from iface in containingType.AllInterfaces
from interfaceMember in iface.GetMembers()
let impl = containingType.FindImplementationForInterfaceMember(interfaceMember)
where symbol.Equals(impl)
select interfaceMember;
return query.ToImmutableArray();
}
public static ImmutableArray<ISymbol> ImplicitInterfaceImplementations(this ISymbol symbol)
=> symbol.ExplicitOrImplicitInterfaceImplementations().Except(symbol.ExplicitInterfaceImplementations()).ToImmutableArray();
public static bool IsOverridable([NotNullWhen(true)] this ISymbol? symbol)
{
// Members can only have overrides if they are virtual, abstract or override and is not sealed.
return symbol is { ContainingType.TypeKind: TypeKind.Class, IsSealed: false } &&
(symbol.IsVirtual || symbol.IsAbstract || symbol.IsOverride);
}
public static bool IsImplementableMember([NotNullWhen(true)] this ISymbol? symbol)
{
if (symbol != null &&
symbol.ContainingType != null &&
symbol.ContainingType.TypeKind == TypeKind.Interface)
{
if (symbol.Kind == SymbolKind.Event)
{
return true;
}
if (symbol.Kind == SymbolKind.Property)
{
return true;
}
if (symbol.Kind == SymbolKind.Method)
{
var methodSymbol = (IMethodSymbol)symbol;
if (methodSymbol.MethodKind is MethodKind.Ordinary or
MethodKind.PropertyGet or
MethodKind.PropertySet or
MethodKind.UserDefinedOperator or
MethodKind.Conversion)
{
return true;
}
}
}
return false;
}
public static INamedTypeSymbol? GetContainingTypeOrThis(this ISymbol symbol)
{
if (symbol is INamedTypeSymbol namedType)
{
return namedType;
}
return symbol.ContainingType;
}
public static bool IsErrorType([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is ITypeSymbol { TypeKind: TypeKind.Error };
public static bool IsModuleType([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is ITypeSymbol { TypeKind: TypeKind.Module };
public static bool IsInterfaceType([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is ITypeSymbol { TypeKind: TypeKind.Interface };
public static bool IsArrayType([NotNullWhen(true)] this ISymbol? symbol)
=> symbol?.Kind == SymbolKind.ArrayType;
public static bool IsTupleType([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is ITypeSymbol { IsTupleType: true };
public static bool IsAnonymousFunction([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IMethodSymbol { MethodKind: MethodKind.AnonymousFunction };
public static bool IsKind([NotNullWhen(true)] this ISymbol? symbol, SymbolKind kind)
=> symbol.MatchesKind(kind);
public static bool MatchesKind([NotNullWhen(true)] this ISymbol? symbol, SymbolKind kind)
=> symbol?.Kind == kind;
public static bool MatchesKind([NotNullWhen(true)] this ISymbol? symbol, SymbolKind kind1, SymbolKind kind2)
{
return symbol != null
&& (symbol.Kind == kind1 || symbol.Kind == kind2);
}
public static bool MatchesKind([NotNullWhen(true)] this ISymbol? symbol, SymbolKind kind1, SymbolKind kind2, SymbolKind kind3)
{
return symbol != null
&& (symbol.Kind == kind1 || symbol.Kind == kind2 || symbol.Kind == kind3);
}
public static bool MatchesKind([NotNullWhen(true)] this ISymbol? symbol, params SymbolKind[] kinds)
{
return symbol != null
&& kinds.Contains(symbol.Kind);
}
public static bool IsReducedExtension([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IMethodSymbol { MethodKind: MethodKind.ReducedExtension };
public static bool IsEnumMember([NotNullWhen(true)] this ISymbol? symbol)
=> symbol?.Kind == SymbolKind.Field && symbol.ContainingType.IsEnumType();
public static bool IsExtensionMethod(this ISymbol symbol)
=> symbol is IMethodSymbol { IsExtensionMethod: true };
public static bool IsLocalFunction([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IMethodSymbol { MethodKind: MethodKind.LocalFunction };
public static bool IsAnonymousOrLocalFunction([NotNullWhen(true)] this ISymbol? symbol)
=> symbol.IsAnonymousFunction() || symbol.IsLocalFunction();
public static bool IsModuleMember([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is { ContainingType.TypeKind: TypeKind.Module };
public static bool IsConstructor([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IMethodSymbol { MethodKind: MethodKind.Constructor };
public static bool IsStaticConstructor([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IMethodSymbol { MethodKind: MethodKind.StaticConstructor };
public static bool IsDestructor([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IMethodSymbol { MethodKind: MethodKind.Destructor };
public static bool IsUserDefinedOperator([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IMethodSymbol { MethodKind: MethodKind.UserDefinedOperator };
public static bool IsConversion([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IMethodSymbol { MethodKind: MethodKind.Conversion };
public static bool IsOrdinaryMethod([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IMethodSymbol { MethodKind: MethodKind.Ordinary };
public static bool IsOrdinaryMethodOrLocalFunction([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IMethodSymbol { MethodKind: MethodKind.Ordinary or MethodKind.LocalFunction };
public static bool IsDelegateType([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is ITypeSymbol { TypeKind: TypeKind.Delegate };
public static bool IsAnonymousType([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is INamedTypeSymbol { IsAnonymousType: true };
public static bool IsNormalAnonymousType([NotNullWhen(true)] this ISymbol? symbol)
=> symbol.IsAnonymousType() && !symbol.IsDelegateType();
public static bool IsAnonymousDelegateType([NotNullWhen(true)] this ISymbol? symbol)
=> symbol.IsAnonymousType() && symbol.IsDelegateType();
public static bool IsAnonymousTypeProperty([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IPropertySymbol && symbol.ContainingType.IsNormalAnonymousType();
public static bool IsTupleField([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IFieldSymbol { ContainingType.IsTupleType: true };
public static bool IsIndexer([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IPropertySymbol { IsIndexer: true };
public static bool IsWriteableFieldOrProperty([NotNullWhen(true)] this ISymbol? symbol)
=> symbol switch
{
IFieldSymbol fieldSymbol => !fieldSymbol.IsReadOnly && !fieldSymbol.IsConst,
IPropertySymbol propertySymbol => !propertySymbol.IsReadOnly,
_ => false,
};
public static bool IsRequired([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IFieldSymbol { IsRequired: true } or IPropertySymbol { IsRequired: true };
public static ITypeSymbol? GetMemberType(this ISymbol? symbol)
=> symbol switch
{
IFieldSymbol fieldSymbol => fieldSymbol.Type,
IPropertySymbol propertySymbol => propertySymbol.Type,
IMethodSymbol methodSymbol => methodSymbol.ReturnType,
IEventSymbol eventSymbol => eventSymbol.Type,
IParameterSymbol parameterSymbol => parameterSymbol.Type,
ILocalSymbol localSymbol => localSymbol.Type,
_ => null,
};
public static int GetArity(this ISymbol symbol)
=> symbol.Kind switch
{
SymbolKind.NamedType => ((INamedTypeSymbol)symbol).Arity,
SymbolKind.Method => ((IMethodSymbol)symbol).Arity,
_ => 0,
};
[return: NotNullIfNotNull(parameterName: nameof(symbol))]
public static ISymbol? GetOriginalUnreducedDefinition(this ISymbol? symbol)
{
if (symbol.IsTupleField())
{
return symbol;
}
if (symbol.IsReducedExtension())
{
// note: ReducedFrom is only a method definition and includes no type arguments.
symbol = ((IMethodSymbol)symbol).GetConstructedReducedFrom();
}
if (symbol.IsFunctionValue())
{
if (symbol.ContainingSymbol is IMethodSymbol method)
{
symbol = method;
if (method.AssociatedSymbol != null)
{
symbol = method.AssociatedSymbol;
}
}
}
if (symbol.IsNormalAnonymousType() || symbol.IsAnonymousTypeProperty())
{
return symbol;
}
if (symbol is IParameterSymbol parameter)
{
var method = parameter.ContainingSymbol as IMethodSymbol;
if (method?.IsReducedExtension() == true)
{
symbol = method.GetConstructedReducedFrom()!.Parameters[parameter.Ordinal + 1];
}
}
return symbol?.OriginalDefinition;
}
public static bool IsFunctionValue([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is ILocalSymbol { IsFunctionValue: true };
public static bool IsThisParameter([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IParameterSymbol { IsThis: true };
[return: NotNullIfNotNull(parameterName: nameof(symbol))]
public static ISymbol? ConvertThisParameterToType(this ISymbol? symbol)
{
if (symbol.IsThisParameter())
{
return ((IParameterSymbol)symbol).Type;
}
return symbol;
}
public static bool IsParams([NotNullWhen(true)] this ISymbol? symbol)
=> symbol.GetParameters() is [.., { IsParams: true }];
public static ImmutableArray<IParameterSymbol> GetParameters(this ISymbol? symbol)
=> symbol switch
{
IMethodSymbol m => m.Parameters,
IPropertySymbol nt => nt.Parameters,
_ => [],
};
public static ImmutableArray<ITypeParameterSymbol> GetTypeParameters(this ISymbol? symbol)
=> symbol switch
{
IMethodSymbol m => m.TypeParameters,
INamedTypeSymbol nt => nt.TypeParameters,
_ => [],
};
public static ImmutableArray<ITypeParameterSymbol> GetAllTypeParameters(this ISymbol? symbol)
{
var results = ArrayBuilder<ITypeParameterSymbol>.GetInstance();
while (symbol != null)
{
results.AddRange(symbol.GetTypeParameters());
symbol = symbol.ContainingType;
}
return results.ToImmutableAndFree();
}
public static ImmutableArray<ITypeSymbol> GetTypeArguments(this ISymbol? symbol)
=> symbol switch
{
IMethodSymbol m => m.TypeArguments,
INamedTypeSymbol nt => nt.TypeArguments,
_ => [],
};
public static ImmutableArray<ITypeSymbol> GetAllTypeArguments(this ISymbol symbol)
{
var results = ArrayBuilder<ITypeSymbol>.GetInstance();
results.AddRange(symbol.GetTypeArguments());
var containingType = symbol.ContainingType;
while (containingType != null)
{
results.AddRange(containingType.GetTypeArguments());
containingType = containingType.ContainingType;
}
return results.ToImmutableAndFree();
}
public static bool IsAttribute([NotNullWhen(true)] this ISymbol? symbol)
=> (symbol as ITypeSymbol)?.IsAttribute() == true;
/// <summary>
/// Returns <see langword="true"/> if the signature of this symbol requires the <see
/// langword="unsafe"/> modifier. For example a method that takes <c>List<int*[]></c>
/// is unsafe, as is <c>int* Goo { get; }</c>. This will return <see langword="false"/> for
/// symbols that cannot have the <see langword="unsafe"/> modifier on them.
/// </summary>
public static bool RequiresUnsafeModifier([NotNullWhen(true)] this ISymbol? member)
{
// TODO(cyrusn): Defer to compiler code to handle this once it can.
return member?.Accept(new RequiresUnsafeModifierVisitor()) == true;
}
public static ITypeSymbol ConvertToType(
this ISymbol? symbol,
Compilation compilation,
bool extensionUsedAsInstance = false)
{
if (symbol is ITypeSymbol type)
{
return type;
}
if (symbol is IMethodSymbol method && method.Parameters.All(p => p.RefKind == RefKind.None))
{
var count = extensionUsedAsInstance ? Math.Max(0, method.Parameters.Length - 1) : method.Parameters.Length;
var skip = extensionUsedAsInstance ? 1 : 0;
// Convert the symbol to Func<...> or Action<...>
var delegateType = compilation.GetTypeByMetadataName(method.ReturnsVoid
? WithArity("System.Action", count)
: WithArity("System.Func", count + 1));
if (delegateType != null)
{
var types = method.Parameters
.Skip(skip)
.Select(p => (p.Type ?? compilation.GetSpecialType(SpecialType.System_Object)).WithNullableAnnotation(p.NullableAnnotation));
if (!method.ReturnsVoid)
{
// +1 for the return type.
types = types.Concat((method.ReturnType ?? compilation.GetSpecialType(SpecialType.System_Object)).WithNullableAnnotation(method.ReturnNullableAnnotation));
}
return delegateType.TryConstruct(types.ToArray());
}
}
// Otherwise, just default to object.
return compilation.ObjectType;
// local functions
static string WithArity(string typeName, int arity)
=> arity > 0 ? typeName + '`' + arity : typeName;
}
public static bool IsStaticType([NotNullWhen(true)] this ISymbol? symbol)
=> symbol != null && symbol.Kind == SymbolKind.NamedType && symbol.IsStatic;
public static bool IsNamespace([NotNullWhen(true)] this ISymbol? symbol)
=> symbol?.Kind == SymbolKind.Namespace;
public static bool IsOrContainsAccessibleAttribute(
[NotNullWhen(true)] this ISymbol? symbol, ISymbol withinType, IAssemblySymbol withinAssembly, CancellationToken cancellationToken)
{
var namespaceOrType = symbol is IAliasSymbol alias ? alias.Target : symbol as INamespaceOrTypeSymbol;
if (namespaceOrType == null)
{
return false;
}
// PERF: Avoid allocating a lambda capture
foreach (var type in namespaceOrType.GetAllTypes(cancellationToken))
{
if (type.IsAttribute() && type.IsAccessibleWithin(withinType ?? withinAssembly))
{
return true;
}
}
return false;
}
public static IEnumerable<IPropertySymbol> GetValidAnonymousTypeProperties(this ISymbol symbol)
{
Contract.ThrowIfFalse(symbol.IsNormalAnonymousType());
return ((INamedTypeSymbol)symbol).GetMembers().OfType<IPropertySymbol>().Where(p => p.CanBeReferencedByName);
}
public static Accessibility ComputeResultantAccessibility(this ISymbol? symbol, ITypeSymbol finalDestination)
{
if (symbol == null)
{
return Accessibility.Private;
}
switch (symbol.DeclaredAccessibility)
{
default:
return symbol.DeclaredAccessibility;
case Accessibility.ProtectedAndInternal:
return symbol.ContainingAssembly.GivesAccessTo(finalDestination.ContainingAssembly)
? Accessibility.ProtectedAndInternal
: Accessibility.Internal;
case Accessibility.ProtectedOrInternal:
return symbol.ContainingAssembly.GivesAccessTo(finalDestination.ContainingAssembly)
? Accessibility.ProtectedOrInternal
: Accessibility.Protected;
}
}
/// <returns>
/// Returns true if symbol is a local variable and its declaring syntax node is
/// after the current position, false otherwise (including for non-local symbols)
/// </returns>
public static bool IsInaccessibleLocal(this ISymbol symbol, int position)
{
if (symbol.Kind != SymbolKind.Local)
{
return false;
}
// Implicitly declared locals (with Option Explicit Off in VB) are scoped to the entire
// method and should always be considered accessible from within the same method.
if (symbol.IsImplicitlyDeclared)
{
return false;
}
var declarationSyntax = symbol.DeclaringSyntaxReferences.Select(r => r.GetSyntax()).FirstOrDefault();
return declarationSyntax != null && position < declarationSyntax.SpanStart;
}
public static bool IsAccessor([NotNullWhen(true)] this ISymbol? symbol)
=> symbol.IsPropertyAccessor() || symbol.IsEventAccessor();
public static bool IsPropertyAccessor([NotNullWhen(true)] this ISymbol? symbol)
=> (symbol as IMethodSymbol)?.MethodKind.IsPropertyAccessor() == true;
public static bool IsEventAccessor([NotNullWhen(true)] this ISymbol? symbol)
=> symbol is IMethodSymbol { MethodKind: MethodKind.EventAdd or MethodKind.EventRaise or MethodKind.EventRemove };
public static bool IsFromSource(this ISymbol symbol)
=> symbol.Locations.Any() && symbol.Locations.All(location => location.IsInSource);
public static bool IsNonImplicitAndFromSource(this ISymbol symbol)
=> !symbol.IsImplicitlyDeclared && symbol.IsFromSource();
public static ITypeSymbol? GetSymbolType(this ISymbol? symbol)
=> symbol switch
{
ILocalSymbol localSymbol => localSymbol.Type,
IFieldSymbol fieldSymbol => fieldSymbol.Type,
IPropertySymbol propertySymbol => propertySymbol.Type,
IParameterSymbol parameterSymbol => parameterSymbol.Type,
IAliasSymbol aliasSymbol => aliasSymbol.Target as ITypeSymbol,
_ => symbol as ITypeSymbol,
};
/// <summary>
/// If the <paramref name="symbol"/> is a method symbol, returns <see langword="true"/> if the method's return type is "awaitable", but not if it's <see langword="dynamic"/>.
/// If the <paramref name="symbol"/> is a type symbol, returns <see langword="true"/> if that type is "awaitable".
/// An "awaitable" is any type that exposes a GetAwaiter method which returns a valid "awaiter". This GetAwaiter method may be an instance method or an extension method.
/// </summary>
public static bool IsAwaitableNonDynamic([NotNullWhen(true)] this ISymbol? symbol, SemanticModel semanticModel, int position)
{
var methodSymbol = symbol as IMethodSymbol;
ITypeSymbol? typeSymbol = null;
if (methodSymbol == null)
{
typeSymbol = symbol as ITypeSymbol;
if (typeSymbol == null)
{
return false;
}
}
else
{
if (methodSymbol.ReturnType == null)
{
return false;
}
}
// otherwise: needs valid GetAwaiter
var potentialGetAwaiters = semanticModel.LookupSymbols(position,
container: typeSymbol ?? methodSymbol!.ReturnType.OriginalDefinition,
name: WellKnownMemberNames.GetAwaiter,
includeReducedExtensionMethods: true);
var getAwaiters = potentialGetAwaiters.OfType<IMethodSymbol>().Where(x => !x.Parameters.Any());
return getAwaiters.Any(VerifyGetAwaiter);
}
public static bool IsValidGetAwaiter(this IMethodSymbol symbol)
=> symbol.Name == WellKnownMemberNames.GetAwaiter &&
VerifyGetAwaiter(symbol);
private static bool VerifyGetAwaiter(IMethodSymbol getAwaiter)
{
var returnType = getAwaiter.ReturnType;
if (returnType == null)
{
return false;
}
// bool IsCompleted { get }
if (!returnType.GetMembers().OfType<IPropertySymbol>().Any(p => p.Name == WellKnownMemberNames.IsCompleted && p.Type.SpecialType == SpecialType.System_Boolean && p.GetMethod != null))
{
return false;
}
var methods = returnType.GetMembers().OfType<IMethodSymbol>();
// NOTE: (vladres) The current version of C# Spec, §7.7.7.3 'Runtime evaluation of await expressions', requires that
// NOTE: the interface method INotifyCompletion.OnCompleted or ICriticalNotifyCompletion.UnsafeOnCompleted is invoked
// NOTE: (rather than any OnCompleted method conforming to a certain pattern).
// NOTE: Should this code be updated to match the spec?
// void OnCompleted(Action)
// Actions are delegates, so we'll just check for delegates.
if (!methods.Any(x => x.Name == WellKnownMemberNames.OnCompleted && x.ReturnsVoid && x.Parameters is [{ Type.TypeKind: TypeKind.Delegate }]))
return false;
// void GetResult() || T GetResult()
return methods.Any(m => m.Name == WellKnownMemberNames.GetResult && !m.Parameters.Any());
}
public static bool IsValidGetEnumerator(this IMethodSymbol symbol)
=> symbol.Name == WellKnownMemberNames.GetEnumeratorMethodName &&
VerifyGetEnumerator(symbol);
private static bool VerifyGetEnumerator(IMethodSymbol getEnumerator)
{
var returnType = getEnumerator.ReturnType;
if (returnType == null)
{
return false;
}
var members = returnType.AllInterfaces.Concat(returnType.GetBaseTypesAndThis())
.SelectMany(x => x.GetMembers())
.Where(x => x.DeclaredAccessibility == Accessibility.Public)
.ToList();
// T Current { get }
if (!members.OfType<IPropertySymbol>().Any(p => p.Name == WellKnownMemberNames.CurrentPropertyName && p.GetMethod != null))
{
return false;
}
// bool MoveNext()
if (!members.OfType<IMethodSymbol>().Any(x =>
{
return x is
{
Name: WellKnownMemberNames.MoveNextMethodName,
ReturnType.SpecialType: SpecialType.System_Boolean,
Parameters.Length: 0,
};
}))
{
return false;
}
return true;
}
public static bool IsValidGetAsyncEnumerator(this IMethodSymbol symbol)
=> symbol.Name == WellKnownMemberNames.GetAsyncEnumeratorMethodName &&
VerifyGetAsyncEnumerator(symbol);
private static bool VerifyGetAsyncEnumerator(IMethodSymbol getAsyncEnumerator)
{
var returnType = getAsyncEnumerator.ReturnType;
if (returnType == null)
{
return false;
}
var members = returnType.AllInterfaces.Concat(returnType.GetBaseTypesAndThis())
.SelectMany(x => x.GetMembers())
.Where(x => x.DeclaredAccessibility == Accessibility.Public)
.ToList();
// T Current { get }
if (!members.OfType<IPropertySymbol>().Any(p => p.Name == WellKnownMemberNames.CurrentPropertyName && p.GetMethod != null))
{
return false;
}
// Task<bool> MoveNext()
// We don't check for the return type, since it can be any awaitable wrapping a boolean,
// which is too complex to be worth checking here.
// We don't check number of parameters since MoveNextAsync allows optional parameters/params
if (!members.OfType<IMethodSymbol>().Any(x => x.Name == WellKnownMemberNames.MoveNextAsyncMethodName))
{
return false;
}
return true;
}
public static bool IsKind<TSymbol>(this ISymbol symbol, SymbolKind kind, [NotNullWhen(true)] out TSymbol? result) where TSymbol : class, ISymbol
{
if (!symbol.IsKind(kind))
{
result = null;
return false;
}
result = (TSymbol)symbol;
return true;
}
/// <summary>
/// Returns true for symbols whose name starts with an underscore and
/// are optionally followed by an integer or other underscores, such as '_', '_1', '_2', '__', '___', etc.
/// These are treated as special discard symbol names.
/// </summary>
public static bool IsSymbolWithSpecialDiscardName(this ISymbol symbol)
=> symbol.Name.StartsWith("_") &&
(symbol.Name.Length == 1 || uint.TryParse(symbol.Name[1..], out _) || symbol.Name.All(n => n.Equals('_')));
/// <summary>
/// Returns <see langword="true"/>, if the symbol is marked with the <see cref="System.ObsoleteAttribute"/>.
/// </summary>
/// <param name="symbol"></param>
/// <returns><see langword="true"/> if the symbol is marked with the <see cref="System.ObsoleteAttribute"/>.</returns>
public static bool IsObsolete(this ISymbol symbol)
=> symbol.GetAttributes().Any(static x => x.AttributeClass is
{
MetadataName: nameof(ObsoleteAttribute),
ContainingNamespace.Name: nameof(System),
});
}
|