|
// 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;
using System.Linq;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
/// <summary>
/// <para>
/// C# 4.0 §10.6: The name, the type parameter list and the formal parameter list of a method define
/// the signature (§3.6) of the method. Specifically, the signature of a method consists of its
/// name, the number of type parameters and the number, modifiers, and types of its formal
/// parameters. For these purposes, any type parameter of the method that occurs in the type of
/// a formal parameter is identified not by its name, but by its ordinal position in the type
/// argument list of the method. The return type is not part of a method's signature, nor are
/// the names of the type parameters or the formal parameters.
/// </para>
/// <para>
/// C# 4.0 §3.6: For the purposes of signatures, the types object and dynamic are considered the
/// same.
/// </para>
/// <para>
/// C# 4.0 §3.6: We implement the rules for ref/out by mapping both to ref. The caller (i.e.
/// checking for proper overrides or partial methods, etc) should check that ref/out are
/// consistent.
/// </para>
/// </summary>
internal sealed class MemberSignatureComparer : IEqualityComparer<Symbol>
{
/// <summary>
/// This instance is used when trying to determine if one member explicitly implements another,
/// according the C# definition.
/// The member signatures are compared without regard to name (including the interface part, if any)
/// and the return types must match.
/// </summary>
public static readonly MemberSignatureComparer ExplicitImplementationComparer = new MemberSignatureComparer(
considerName: false,
considerExplicitlyImplementedInterfaces: false,
considerReturnType: true,
considerTypeConstraints: false,
refKindCompareMode: RefKindCompareMode.ConsiderDifferences | RefKindCompareMode.AllowRefReadonlyVsInMismatch,
considerCallingConvention: true,
typeComparison: TypeCompareKind.AllIgnoreOptions);
/// <summary>
/// This instance is used when trying to determine if one member implicitly implements another,
/// according to the C# definition.
/// The member names, parameters, and (return) types must match. Custom modifiers are ignored.
/// </summary>
/// <remarks>
/// One would expect this comparer to have requireSourceMethod = true, but it doesn't because (for source types)
/// we allow inexact matching of custom modifiers when computing implicit member implementations. Consider the
/// following scenario: interface I has a method M with custom modifiers C1, source type ST includes I in its
/// interface list but has no method M, and metadata type MT has a method M with custom modifiers C2.
/// In this scenario, we want to compare I.M to MT.M without regard to custom modifiers, because if C1 != C2,
/// we can just synthesize an explicit implementation of I.M in ST that calls MT.M.
/// </remarks>
public static readonly MemberSignatureComparer CSharpImplicitImplementationComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: true,
considerReturnType: true,
considerTypeConstraints: false, // constraints are checked by caller instead
considerCallingConvention: true,
refKindCompareMode: RefKindCompareMode.ConsiderDifferences | RefKindCompareMode.AllowRefReadonlyVsInMismatch,
typeComparison: TypeCompareKind.AllIgnoreOptions);
/// <summary>
/// This instance is used as a fallback when it is determined that one member does not implicitly implement
/// another. It applies a looser check to determine whether the proposed implementation should be reported
/// as "close".
/// </summary>
public static readonly MemberSignatureComparer CSharpCloseImplicitImplementationComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: true,
considerReturnType: false,
considerTypeConstraints: false,
considerCallingConvention: false,
refKindCompareMode: RefKindCompareMode.ConsiderDifferences | RefKindCompareMode.AllowRefReadonlyVsInMismatch,
typeComparison: TypeCompareKind.AllIgnoreOptions); //shouldn't actually matter for source members
/// <summary>
/// This instance is used to determine if two C# member declarations in source conflict with each other.
/// Names, arities, and parameter types are considered.
/// Return types, type parameter constraints, custom modifiers, and parameter ref kinds, etc are ignored.
/// </summary>
/// <remarks>
/// This does the same comparison that MethodSignature used to do.
/// </remarks>
public static readonly MemberSignatureComparer DuplicateSourceComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: true,
considerReturnType: false,
considerTypeConstraints: false,
considerCallingConvention: false,
refKindCompareMode: RefKindCompareMode.DoNotConsiderDifferences,
typeComparison: TypeCompareKind.AllIgnoreOptions);
/// <summary>
/// This instance is used to determine if some API specific to records is explicitly declared.
/// It is the same as <see cref="DuplicateSourceComparer"/> except it considers ref kinds as well.
/// </summary>
public static readonly MemberSignatureComparer RecordAPISignatureComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: true,
considerReturnType: false,
considerTypeConstraints: false,
considerCallingConvention: false,
refKindCompareMode: RefKindCompareMode.ConsiderDifferences,
typeComparison: TypeCompareKind.AllIgnoreOptions);
/// <summary>
/// This instance is used to determine if a partial method implementation matches the definition.
/// It is the same as <see cref="DuplicateSourceComparer"/> except it considers ref kinds as well.
/// </summary>
public static readonly MemberSignatureComparer PartialMethodsComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: true,
considerReturnType: false,
considerTypeConstraints: false,
considerCallingConvention: false,
refKindCompareMode: RefKindCompareMode.ConsiderDifferences,
typeComparison: TypeCompareKind.AllIgnoreOptions);
/// <summary>
/// This instance is used to determine if a partial method implementation matches the definition,
/// including differences ignored by the runtime.
/// </summary>
public static readonly MemberSignatureComparer PartialMethodsStrictComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: true,
considerReturnType: true,
considerTypeConstraints: false,
considerCallingConvention: false,
refKindCompareMode: RefKindCompareMode.ConsiderDifferences,
typeComparison: TypeCompareKind.ObliviousNullableModifierMatchesAny);
/// <summary>
/// Determines if an interceptor has a compatible signature with an interceptable method.
/// NB: when a classic extension method is intercepting an instance method call, a normalization to 'ReducedExtensionMethodSymbol' must be performed first.
/// </summary>
public static readonly MemberSignatureComparer InterceptorsComparer = new MemberSignatureComparer(
considerName: false,
considerExplicitlyImplementedInterfaces: false,
considerReturnType: true,
considerTypeConstraints: false,
considerCallingConvention: false,
refKindCompareMode: RefKindCompareMode.ConsiderDifferences,
considerArity: false,
typeComparison: TypeCompareKind.AllIgnoreOptions);
/// <summary>
/// Determines if an interceptor has a compatible signature with an interceptable method.
/// If methods are considered equal by <see cref="InterceptorsComparer"/>, but not equal by this comparer, a warning is reported.
/// NB: when a classic extension method is intercepting an instance method call, a normalization to 'ReducedExtensionMethodSymbol' must be performed first.
/// </summary>
public static readonly MemberSignatureComparer InterceptorsStrictComparer = new MemberSignatureComparer(
considerName: false,
considerExplicitlyImplementedInterfaces: false,
considerReturnType: true,
considerTypeConstraints: false,
considerCallingConvention: false,
refKindCompareMode: RefKindCompareMode.ConsiderDifferences,
considerArity: false,
typeComparison: TypeCompareKind.AllNullableIgnoreOptions);
/// <summary>
/// This instance is used to check whether one member overrides another, according to the C# definition.
/// </summary>
public static readonly MemberSignatureComparer CSharpOverrideComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: false,
considerReturnType: false,
considerTypeConstraints: false,
considerCallingConvention: false, //ignore static-ness
refKindCompareMode: RefKindCompareMode.ConsiderDifferences | RefKindCompareMode.AllowRefReadonlyVsInMismatch,
typeComparison: TypeCompareKind.AllIgnoreOptions);
/// <summary>
/// This instance checks whether two signatures match including tuples names, in both return type and parameters.
/// It is used to detect tuple-name-only differences.
/// </summary>
private static readonly MemberSignatureComparer CSharpWithTupleNamesComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: false,
considerReturnType: true,
considerTypeConstraints: false,
considerCallingConvention: false, //ignore static-ness
refKindCompareMode: RefKindCompareMode.DoNotConsiderDifferences,
typeComparison: TypeCompareKind.AllIgnoreOptions & ~TypeCompareKind.IgnoreTupleNames);
/// <summary>
/// This instance checks whether two signatures match excluding tuples names, in both return type and parameters.
/// It is used to detect tuple-name-only differences.
/// </summary>
private static readonly MemberSignatureComparer CSharpWithoutTupleNamesComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: false,
considerReturnType: true,
considerTypeConstraints: false,
considerCallingConvention: false, //ignore static-ness
refKindCompareMode: RefKindCompareMode.DoNotConsiderDifferences,
typeComparison: TypeCompareKind.AllIgnoreOptions);
/// <summary>
/// This instance is used to check whether one property or event overrides another, according to the C# definition.
/// <para>NOTE: C# ignores accessor member names.</para>
/// </summary>
public static readonly MemberSignatureComparer CSharpAccessorOverrideComparer = new MemberSignatureComparer(
considerName: false,
considerExplicitlyImplementedInterfaces: false, //Bug: DevDiv #15775
considerReturnType: false,
considerTypeConstraints: false,
considerCallingConvention: false, //ignore static-ness
refKindCompareMode: RefKindCompareMode.ConsiderDifferences | RefKindCompareMode.AllowRefReadonlyVsInMismatch,
typeComparison: TypeCompareKind.AllIgnoreOptions);
/// <summary>
/// Same as <see cref="CSharpOverrideComparer"/> except that it pays attention to custom modifiers and return type.
/// Normally, the return type isn't considered during overriding, but this comparer is actually used to find
/// exact matches (i.e. before tie-breaking takes place amongst close matches).
/// </summary>
public static readonly MemberSignatureComparer CSharpCustomModifierOverrideComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: false,
considerReturnType: true,
considerTypeConstraints: false,
considerCallingConvention: false, //ignore static-ness
refKindCompareMode: RefKindCompareMode.ConsiderDifferences | RefKindCompareMode.AllowRefReadonlyVsInMismatch,
typeComparison: TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes | TypeCompareKind.IgnoreNativeIntegers);
/// <summary>
/// If this returns false, then the real override comparer (whichever one is appropriate for the scenario)
/// will also return false.
/// </summary>
internal static readonly MemberSignatureComparer SloppyOverrideComparer = new MemberSignatureComparer(
considerName: false,
considerExplicitlyImplementedInterfaces: false,
considerReturnType: false,
considerTypeConstraints: false,
considerCallingConvention: false, //ignore static-ness
refKindCompareMode: RefKindCompareMode.DoNotConsiderDifferences,
typeComparison: TypeCompareKind.IgnoreCustomModifiersAndArraySizesAndLowerBounds | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes | TypeCompareKind.IgnoreDynamicAndTupleNames);
/// <summary>
/// This instance is intended to reflect the definition of signature equality used by the runtime
/// (<a href="http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf">ECMA-335</a>, Partition I, §8.6.1.6 Signature Matching).
/// It considers return type, name, parameters, calling convention, and custom modifiers, but ignores
/// the difference between <see cref="RefKind.Out"/> and <see cref="RefKind.Ref"/>.
/// </summary>
public static readonly MemberSignatureComparer RuntimeSignatureComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: false,
considerReturnType: true,
considerTypeConstraints: false,
considerCallingConvention: true,
refKindCompareMode: RefKindCompareMode.DoNotConsiderDifferences,
typeComparison: TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes | TypeCompareKind.IgnoreNativeIntegers);
/// <summary>
/// Same as <see cref="RuntimeSignatureComparer"/>, but in addition ignores name.
/// </summary>
public static readonly MemberSignatureComparer RuntimeExplicitImplementationSignatureComparer = new MemberSignatureComparer(
considerName: false,
considerExplicitlyImplementedInterfaces: false,
considerReturnType: true,
considerTypeConstraints: false,
considerCallingConvention: true,
refKindCompareMode: RefKindCompareMode.DoNotConsiderDifferences,
typeComparison: TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes | TypeCompareKind.IgnoreNativeIntegers);
/// <summary>
/// Same as <see cref="RuntimeSignatureComparer"/>, but distinguishes between <c>ref</c> and <c>out</c>. During override resolution,
/// if we find two methods that match except for <c>ref</c>/<c>out</c>, we want to prefer the one that matches, even
/// if the runtime doesn't.
/// </summary>
public static readonly MemberSignatureComparer RuntimePlusRefOutSignatureComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: false,
considerReturnType: true,
considerTypeConstraints: false,
considerCallingConvention: true,
refKindCompareMode: RefKindCompareMode.ConsiderDifferences | RefKindCompareMode.AllowRefReadonlyVsInMismatch,
typeComparison: TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes | TypeCompareKind.IgnoreNativeIntegers);
/// <summary>
/// This instance is the same as RuntimeSignatureComparer.
/// CONSIDER: just use RuntimeSignatureComparer?
/// </summary>
public static readonly MemberSignatureComparer RuntimeImplicitImplementationComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: true,
considerReturnType: true,
considerTypeConstraints: false, // constraints are checked by caller instead
considerCallingConvention: true,
refKindCompareMode: RefKindCompareMode.DoNotConsiderDifferences,
typeComparison: TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes | TypeCompareKind.IgnoreNativeIntegers);
/// <summary>
/// This instance is used to search for members that have identical signatures in every regard.
/// </summary>
public static readonly MemberSignatureComparer RetargetedExplicitImplementationComparer = new MemberSignatureComparer(
considerName: true,
considerExplicitlyImplementedInterfaces: false, //we'll be comparing interface members anyway
considerReturnType: true,
considerTypeConstraints: false,
considerCallingConvention: true,
refKindCompareMode: RefKindCompareMode.ConsiderDifferences | RefKindCompareMode.AllowRefReadonlyVsInMismatch,
typeComparison: TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes | TypeCompareKind.IgnoreNativeIntegers); //if it was a true explicit impl, we expect it to remain so after retargeting
/// <summary>
/// This instance is used for performing approximate overload resolution of documentation
/// comment <c>cref</c> attributes. It ignores the name, because the candidates were all found by lookup.
/// </summary>
public static readonly MemberSignatureComparer CrefComparer = new MemberSignatureComparer(
considerName: false, //handled by lookup
considerExplicitlyImplementedInterfaces: false,
considerReturnType: false,
considerTypeConstraints: false,
considerCallingConvention: false, //ignore static-ness
refKindCompareMode: RefKindCompareMode.ConsiderDifferences,
typeComparison: TypeCompareKind.IgnoreCustomModifiersAndArraySizesAndLowerBounds | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes | TypeCompareKind.IgnoreDynamicAndTupleNames);
/// <summary>
/// Compare signatures of methods from a method group (only used in logic for older language version).
/// </summary>
internal static readonly MemberSignatureComparer CSharp10MethodGroupSignatureComparer = new MemberSignatureComparer(
considerName: false,
considerExplicitlyImplementedInterfaces: false,
considerReturnType: true,
considerTypeConstraints: false,
refKindCompareMode: RefKindCompareMode.ConsiderDifferences,
considerCallingConvention: false,
considerArity: true,
considerDefaultValues: true,
typeComparison: TypeCompareKind.AllIgnoreOptions);
/// <summary>
/// Compare signatures of methods from a method group.
/// </summary>
internal static readonly MemberSignatureComparer MethodGroupSignatureComparer = new MemberSignatureComparer(
considerName: false,
considerExplicitlyImplementedInterfaces: false,
considerReturnType: true,
considerTypeConstraints: false,
refKindCompareMode: RefKindCompareMode.ConsiderDifferences,
considerCallingConvention: false,
considerArity: false,
considerDefaultValues: true,
typeComparison: TypeCompareKind.AllIgnoreOptions);
// Compare the "unqualified" part of the member name (no explicit part)
private readonly bool _considerName;
// Compare the interfaces implemented (as symbols, to avoid ambiguous representations)
private readonly bool _considerExplicitlyImplementedInterfaces;
// Compare the type symbols of the return types
private readonly bool _considerReturnType;
// Compare the type constraints
private readonly bool _considerTypeConstraints;
// Compare the arity (type parameter count)
private readonly bool _considerArity;
// Compare the full calling conventions. Still compares varargs if false.
private readonly bool _considerCallingConvention;
// Compare explicit default values
private readonly bool _considerDefaultValues;
private readonly RefKindCompareMode _refKindCompareMode;
// Equality options for parameter types and return types (if return is considered).
private readonly TypeCompareKind _typeComparison;
private MemberSignatureComparer(
bool considerName,
bool considerExplicitlyImplementedInterfaces,
bool considerReturnType,
bool considerTypeConstraints,
bool considerCallingConvention,
RefKindCompareMode refKindCompareMode,
bool considerArity = true,
bool considerDefaultValues = false,
TypeCompareKind typeComparison = TypeCompareKind.IgnoreDynamic | TypeCompareKind.IgnoreNativeIntegers)
{
Debug.Assert(!considerExplicitlyImplementedInterfaces || considerName, "Doesn't make sense to consider interfaces separately from name.");
Debug.Assert(!considerTypeConstraints || considerArity, "If you consider type constraints, you must also consider arity");
_considerName = considerName;
_considerExplicitlyImplementedInterfaces = considerExplicitlyImplementedInterfaces;
_considerReturnType = considerReturnType;
_considerTypeConstraints = considerTypeConstraints;
_considerCallingConvention = considerCallingConvention;
_refKindCompareMode = refKindCompareMode;
_considerArity = considerArity;
_considerDefaultValues = considerDefaultValues;
_typeComparison = typeComparison;
Debug.Assert((_typeComparison & TypeCompareKind.FunctionPointerRefMatchesOutInRefReadonly) == 0,
$"Rely on the {nameof(refKindCompareMode)} flag to set this to ensure all cases are handled.");
Debug.Assert(_refKindCompareMode == RefKindCompareMode.DoNotConsiderDifferences ||
(_refKindCompareMode & RefKindCompareMode.ConsiderDifferences) != 0,
$"Cannot set {nameof(RefKindCompareMode)} flags without {nameof(RefKindCompareMode.ConsiderDifferences)}.");
if ((refKindCompareMode & RefKindCompareMode.ConsiderDifferences) == 0)
{
_typeComparison |= TypeCompareKind.FunctionPointerRefMatchesOutInRefReadonly;
}
}
#region IEqualityComparer<Symbol> Members
public bool Equals(Symbol? member1, Symbol? member2)
{
if (ReferenceEquals(member1, member2))
{
return true;
}
if (member1 is null || member2 is null || member1.Kind != member2.Kind)
{
return false;
}
bool sawInterfaceInName1 = false;
bool sawInterfaceInName2 = false;
if (_considerName)
{
string name1 = ExplicitInterfaceHelpers.GetMemberNameWithoutInterfaceName(member1.Name);
string name2 = ExplicitInterfaceHelpers.GetMemberNameWithoutInterfaceName(member2.Name);
sawInterfaceInName1 = name1 != member1.Name;
sawInterfaceInName2 = name2 != member2.Name;
if (name1 != name2)
{
return false;
}
}
// NB: up to, and including, this check, we have not actually forced the (type) parameters
// to be expanded - we're only using the counts.
if (_considerArity && (member1.GetMemberArity() != member2.GetMemberArity()))
{
return false;
}
if (member1.GetParameterCount() != member2.GetParameterCount())
{
return false;
}
TypeMap? typeMap1 = GetTypeMap(member1);
TypeMap? typeMap2 = GetTypeMap(member2);
if (_considerReturnType && !HaveSameReturnTypes(member1, typeMap1, member2, typeMap2, _typeComparison))
{
return false;
}
if (member1.GetParameterCount() > 0 && !HaveSameParameterTypes(member1.GetParameters().AsSpan(), typeMap1, member2.GetParameters().AsSpan(), typeMap2,
_refKindCompareMode, considerDefaultValues: _considerDefaultValues, _typeComparison))
{
return false;
}
if (_considerCallingConvention)
{
if (GetCallingConvention(member1) != GetCallingConvention(member2))
{
return false;
}
}
else
{
if (IsVarargMethod(member1) != IsVarargMethod(member2))
{
return false;
}
}
if (_considerExplicitlyImplementedInterfaces)
{
if (sawInterfaceInName1 != sawInterfaceInName2)
{
return false;
}
// The purpose of this check is to determine whether the interface parts of the member names agree,
// but to do so using robust symbolic checks, rather than syntactic ones. Therefore, if neither member
// name contains an interface name, this check is not relevant.
// Phrased differently, the explicitly implemented interface is not part of the signature unless it's
// part of the name.
if (sawInterfaceInName1)
{
Debug.Assert(sawInterfaceInName2);
// May avoid realizing interface members.
if (member1.IsExplicitInterfaceImplementation() != member2.IsExplicitInterfaceImplementation())
{
return false;
}
// By comparing symbols, rather than syntax, we gain the flexibility of ignoring whitespace
// and gracefully accepting multiple names for the same (or equivalent) types (e.g. "I<int>.M"
// vs "I<System.Int32>.M"), but we lose the connection with the name. For example, in metadata,
// a method name "I.M" could have nothing to do with "I" but explicitly implement interface "I2".
// We will behave as if the method was really named "I2.M". Furthermore, in metadata, a method
// can explicitly implement more than one interface method, in which case it doesn't really
// make sense to pretend that all of them are part of the signature.
var explicitInterfaceImplementations1 = member1.GetExplicitInterfaceImplementations();
var explicitInterfaceImplementations2 = member2.GetExplicitInterfaceImplementations();
if (!explicitInterfaceImplementations1.SetEquals(explicitInterfaceImplementations2, SymbolEqualityComparer.ConsiderEverything))
{
return false;
}
}
}
return !_considerTypeConstraints || HaveSameConstraints(member1, typeMap1, member2, typeMap2);
}
public int GetHashCode(Symbol? member)
{
int hash = 1;
if (member is not null)
{
hash = Hash.Combine((int)member.Kind, hash);
if (_considerName)
{
hash = Hash.Combine(ExplicitInterfaceHelpers.GetMemberNameWithoutInterfaceName(member.Name), hash);
// CONSIDER: could use interface type, but that might be quite expensive
}
if (_considerReturnType && member.GetMemberArity() == 0 &&
(_typeComparison & TypeCompareKind.AllIgnoreOptions) == 0) // If it is generic, then type argument might be in return type.
{
hash = Hash.Combine(member.GetTypeOrReturnType().GetHashCode(), hash);
}
// CONSIDER: modify hash for constraints?
if (member.Kind != SymbolKind.Field)
{
hash = Hash.Combine(member.GetMemberArity(), hash);
hash = Hash.Combine(member.GetParameterCount(), hash);
}
}
return hash;
}
#endregion
public static bool HaveSameReturnTypes(Symbol member1, TypeMap? typeMap1, Symbol member2, TypeMap? typeMap2, TypeCompareKind typeComparison)
{
RefKind refKind1;
TypeWithAnnotations unsubstitutedReturnType1;
ImmutableArray<CustomModifier> refCustomModifiers1;
member1.GetTypeOrReturnType(out refKind1, out unsubstitutedReturnType1, out refCustomModifiers1);
RefKind refKind2;
TypeWithAnnotations unsubstitutedReturnType2;
ImmutableArray<CustomModifier> refCustomModifiers2;
member2.GetTypeOrReturnType(out refKind2, out unsubstitutedReturnType2, out refCustomModifiers2);
// short-circuit type map building in the easiest cases
if (refKind1 != refKind2)
{
return false;
}
var isVoid1 = unsubstitutedReturnType1.IsVoidType();
var isVoid2 = unsubstitutedReturnType2.IsVoidType();
if (isVoid1 != isVoid2)
{
return false;
}
if (isVoid1)
{
if ((typeComparison & TypeCompareKind.IgnoreCustomModifiersAndArraySizesAndLowerBounds) != 0 ||
(unsubstitutedReturnType1.CustomModifiers.IsEmpty && unsubstitutedReturnType2.CustomModifiers.IsEmpty))
{
return true;
}
}
var returnType1 = SubstituteType(typeMap1, unsubstitutedReturnType1);
var returnType2 = SubstituteType(typeMap2, unsubstitutedReturnType2);
if (!returnType1.Equals(returnType2, typeComparison))
{
return false;
}
if (((typeComparison & TypeCompareKind.IgnoreCustomModifiersAndArraySizesAndLowerBounds) == 0) &&
!HaveSameCustomModifiers(refCustomModifiers1, typeMap1, refCustomModifiers2, typeMap2))
{
return false;
}
return true;
}
private static TypeMap? GetTypeMap(Symbol member)
{
var typeParameters = member.GetMemberTypeParameters();
return typeParameters.IsEmpty ?
null :
new TypeMap(
typeParameters,
IndexedTypeParameterSymbol.Take(member.GetMemberArity()),
true);
}
private static bool HaveSameConstraints(Symbol member1, TypeMap? typeMap1, Symbol member2, TypeMap? typeMap2)
{
Debug.Assert(member1.GetMemberArity() == member2.GetMemberArity());
int arity = member1.GetMemberArity();
if (arity == 0)
{
return true;
}
var typeParameters1 = member1.GetMemberTypeParameters();
var typeParameters2 = member2.GetMemberTypeParameters();
return HaveSameConstraints(typeParameters1, typeMap1, typeParameters2, typeMap2);
}
public static bool HaveSameConstraints(ImmutableArray<TypeParameterSymbol> typeParameters1, TypeMap? typeMap1, ImmutableArray<TypeParameterSymbol> typeParameters2, TypeMap? typeMap2)
{
Debug.Assert(typeParameters1.Length == typeParameters2.Length);
int arity = typeParameters1.Length;
for (int i = 0; i < arity; i++)
{
if (!HaveSameConstraints(typeParameters1[i], typeMap1, typeParameters2[i], typeMap2))
{
return false;
}
}
return true;
}
public static bool HaveSameConstraints(TypeParameterSymbol typeParameter1, TypeMap? typeMap1, TypeParameterSymbol typeParameter2, TypeMap? typeMap2)
{
// Spec 13.4.3: Implementation of generic methods.
if ((typeParameter1.HasConstructorConstraint != typeParameter2.HasConstructorConstraint) ||
(typeParameter1.HasReferenceTypeConstraint != typeParameter2.HasReferenceTypeConstraint) ||
(typeParameter1.HasValueTypeConstraint != typeParameter2.HasValueTypeConstraint) ||
(typeParameter1.AllowsRefLikeType != typeParameter2.AllowsRefLikeType) ||
(typeParameter1.HasUnmanagedTypeConstraint != typeParameter2.HasUnmanagedTypeConstraint) ||
(typeParameter1.Variance != typeParameter2.Variance))
{
return false;
}
return HaveSameTypeConstraints(typeParameter1, typeMap1, typeParameter2, typeMap2, SymbolEqualityComparer.IgnoringDynamicTupleNamesAndNullability);
}
private static bool HaveSameTypeConstraints(TypeParameterSymbol typeParameter1, TypeMap? typeMap1, TypeParameterSymbol typeParameter2, TypeMap? typeMap2, IEqualityComparer<TypeSymbol> comparer)
{
// Check that constraintTypes1 is a subset of constraintTypes2 and
// also that constraintTypes2 is a subset of constraintTypes1
// (see SymbolPreparer::CheckImplicitImplConstraints).
var constraintTypes1 = typeParameter1.ConstraintTypesNoUseSiteDiagnostics;
var constraintTypes2 = typeParameter2.ConstraintTypesNoUseSiteDiagnostics;
// The two sets of constraints may differ in size but still be considered
// the same (duplicated constraints, ignored "object" constraints), but
// if both are zero size, the sets must be equal.
if ((constraintTypes1.Length == 0) && (constraintTypes2.Length == 0))
{
return true;
}
var substitutedTypes1 = new HashSet<TypeSymbol>(comparer);
var substitutedTypes2 = new HashSet<TypeSymbol>(comparer);
SubstituteConstraintTypes(constraintTypes1, typeMap1, substitutedTypes1);
SubstituteConstraintTypes(constraintTypes2, typeMap2, substitutedTypes2);
return AreConstraintTypesSubset(substitutedTypes1, substitutedTypes2, typeParameter2) &&
AreConstraintTypesSubset(substitutedTypes2, substitutedTypes1, typeParameter1);
}
public static bool HaveSameNullabilityInConstraints(TypeParameterSymbol typeParameter1, TypeMap typeMap1, TypeParameterSymbol typeParameter2, TypeMap typeMap2)
{
if (!typeParameter1.IsValueType)
{
bool? isNotNullable1 = typeParameter1.IsNotNullable;
bool? isNotNullable2 = typeParameter2.IsNotNullable;
if (isNotNullable1.HasValue && isNotNullable2.HasValue &&
isNotNullable1.GetValueOrDefault() != isNotNullable2.GetValueOrDefault())
{
return false;
}
}
return HaveSameTypeConstraints(typeParameter1, typeMap1, typeParameter2, typeMap2, SymbolEqualityComparer.AllIgnoreOptionsPlusNullableWithUnknownMatchesAny);
}
/// <summary>
/// Returns true if the first set of constraint types
/// is a subset of the second set.
/// </summary>
private static bool AreConstraintTypesSubset(HashSet<TypeSymbol> constraintTypes1, HashSet<TypeSymbol> constraintTypes2, TypeParameterSymbol typeParameter2)
{
foreach (var constraintType in constraintTypes1)
{
// Skip object type (spec. 13.4.3).
if (constraintType.SpecialType == SpecialType.System_Object)
{
continue;
}
if (constraintTypes2.Contains(constraintType))
{
continue;
}
// The struct constraint implies a System.ValueType constraint
// type which may be explicit in the other type parameter
// constraints (through type substitution in derived types).
if ((constraintType.SpecialType == SpecialType.System_ValueType) &&
typeParameter2.HasValueTypeConstraint)
{
continue;
}
return false;
}
return true;
}
private static void SubstituteConstraintTypes(ImmutableArray<TypeWithAnnotations> types, TypeMap? typeMap, HashSet<TypeSymbol> result)
{
foreach (var type in types)
{
result.Add(SubstituteType(typeMap, type).Type);
}
}
internal static bool HaveSameParameterTypes(
ReadOnlySpan<ParameterSymbol> params1,
TypeMap? typeMap1,
ReadOnlySpan<ParameterSymbol> params2,
TypeMap? typeMap2,
RefKindCompareMode refKindCompareMode,
bool considerDefaultValues,
TypeCompareKind typeComparison)
{
Debug.Assert(params1.Length == params2.Length);
var numParams = params1.Length;
for (int i = 0; i < numParams; i++)
{
var param1 = params1[i];
var param2 = params2[i];
var type1 = SubstituteType(typeMap1, param1.TypeWithAnnotations);
var type2 = SubstituteType(typeMap2, param2.TypeWithAnnotations);
if (!type1.Equals(type2, typeComparison))
{
return false;
}
if (considerDefaultValues && param1.ExplicitDefaultConstantValue != param2.ExplicitDefaultConstantValue)
{
return false;
}
if ((typeComparison & TypeCompareKind.IgnoreCustomModifiersAndArraySizesAndLowerBounds) == 0 &&
!HaveSameCustomModifiers(param1.RefCustomModifiers, typeMap1, param2.RefCustomModifiers, typeMap2))
{
return false;
}
var refKind1 = param1.RefKind;
var refKind2 = param2.RefKind;
// Metadata signatures don't distinguish ref/out, but C# does - even when comparing metadata method signatures.
if ((refKindCompareMode & RefKindCompareMode.ConsiderDifferences) != 0)
{
if (!areRefKindsCompatible(refKindCompareMode, refKind1, refKind2))
{
return false;
}
}
else
{
if ((refKind1 == RefKind.None) != (refKind2 == RefKind.None))
{
return false;
}
}
}
return true;
static bool areRefKindsCompatible(RefKindCompareMode refKindCompareMode, RefKind refKind1, RefKind refKind2)
{
if (refKind1 == refKind2)
{
return true;
}
if ((refKindCompareMode & RefKindCompareMode.AllowRefReadonlyVsInMismatch) != 0)
{
return (refKind1, refKind2) is (RefKind.RefReadOnlyParameter, RefKind.In) or (RefKind.In, RefKind.RefReadOnlyParameter);
}
return false;
}
}
private static TypeWithAnnotations SubstituteType(TypeMap? typeMap, TypeWithAnnotations typeSymbol)
{
return typeMap == null ? typeSymbol : typeSymbol.SubstituteType(typeMap);
}
private static bool HaveSameCustomModifiers(ImmutableArray<CustomModifier> customModifiers1, TypeMap? typeMap1, ImmutableArray<CustomModifier> customModifiers2, TypeMap? typeMap2)
{
// the runtime compares custom modifiers using (effectively) SequenceEqual
return SubstituteModifiers(typeMap1, customModifiers1).SequenceEqual(SubstituteModifiers(typeMap2, customModifiers2));
}
private static ImmutableArray<CustomModifier> SubstituteModifiers(TypeMap? typeMap, ImmutableArray<CustomModifier> customModifiers)
{
return typeMap == null ? customModifiers : typeMap.SubstituteCustomModifiers(customModifiers);
}
private static Cci.CallingConvention GetCallingConvention(Symbol member)
{
switch (member.Kind)
{
case SymbolKind.Method:
return ((MethodSymbol)member).CallingConvention;
case SymbolKind.Property: //NOTE: Not using PropertySymbol.CallingConvention
case SymbolKind.Event:
return member.IsStatic ? 0 : Cci.CallingConvention.HasThis;
default:
throw ExceptionUtilities.UnexpectedValue(member.Kind);
}
}
private static bool IsVarargMethod(Symbol member)
{
return member.Kind == SymbolKind.Method && ((MethodSymbol)member).IsVararg;
}
/// <summary>
/// Do the members differ in terms of tuple names (both in their return type and parameters), but would match ignoring names?
///
/// We'll look at the result of equality without tuple names (1) and with tuple names (2).
/// The question is whether there is a change in tuple element names only (3).
///
/// member1 vs. member2 | (1) | (2) | (3) |
/// <c>(int a, int b) M()</c> vs. <c>(int a, int b) M()</c> | yes | yes | match |
/// <c>(int a, int b) M()</c> vs. <c>(int x, int y) M()</c> | yes | no | different |
/// <c>void M((int a, int b))</c> vs. <c>void M((int x, int y))</c> | yes | no | different |
/// <c>int M()</c> vs. <c>string M()</c> | no | no | match |
///
/// </summary>
internal static bool ConsideringTupleNamesCreatesDifference(Symbol member1, Symbol member2)
{
return !CSharpWithTupleNamesComparer.Equals(member1, member2) &&
CSharpWithoutTupleNamesComparer.Equals(member1, member2);
}
[Flags]
internal enum RefKindCompareMode
{
/// <summary>
/// Ref parameter modifiers are ignored.
/// </summary>
DoNotConsiderDifferences = 0,
/// <summary>
/// Parameters with different ref modifiers are considered different.
/// </summary>
ConsiderDifferences = 1 << 0,
/// <summary>
/// 'in'/'ref readonly' modifiers are considered equivalent.
/// </summary>
AllowRefReadonlyVsInMismatch = 1 << 1,
}
}
}
|