diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TypeDesc.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeDesc.cs index 51cf7291cdd748..480739bb516cf5 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/TypeDesc.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeDesc.cs @@ -25,6 +25,8 @@ public override bool Equals(object o) { // Its only valid to compare two TypeDescs in the same context Debug.Assert(o is not TypeDesc || object.ReferenceEquals(((TypeDesc)o).Context, this.Context)); + if (o is TypeDesc) + return this.IsEquivalentTo((TypeDesc)o); return object.ReferenceEquals(this, o); } @@ -33,14 +35,18 @@ public override bool Equals(object o) { // Its only valid to compare two TypeDescs in the same context Debug.Assert(left is null || right is null || object.ReferenceEquals(left.Context, right.Context)); - return object.ReferenceEquals(left, right); + if (left is null) + return object.ReferenceEquals(left, right); + return left.IsEquivalentTo(right); } public static bool operator !=(TypeDesc left, TypeDesc right) { // Its only valid to compare two TypeDescs in the same context Debug.Assert(left is null || right is null || object.ReferenceEquals(left.Context, right.Context)); - return !object.ReferenceEquals(left, right); + if (left is null) + return !object.ReferenceEquals(left, right); + return !left.IsEquivalentTo(right); } #endif @@ -692,5 +698,15 @@ public bool IsIDynamicInterfaceCastable return (GetTypeFlags(TypeFlags.IsIDynamicInterfaceCastable | TypeFlags.IsIDynamicInterfaceCastableComputed) & TypeFlags.IsIDynamicInterfaceCastable) != 0; } } + + /// + /// Determines whether two types have the same identity and are eligible for type equivalence. + /// + public virtual bool IsEquivalentTo(TypeDesc typeDesc) + { + // Its only valid to compare two TypeDescs in the same context + Debug.Assert(typeDesc is null || object.ReferenceEquals(this.Context, typeDesc.Context)); + return object.ReferenceEquals(this, typeDesc); + } } } diff --git a/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaType.cs b/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaType.cs index 073cb7a4fdfb2b..9a34a70a6c2263 100644 --- a/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaType.cs +++ b/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaType.cs @@ -604,5 +604,57 @@ public override PInvokeStringFormat PInvokeStringFormat return (PInvokeStringFormat)(_typeDefinition.Attributes & TypeAttributes.StringFormatMask); } } + + public override bool IsEquivalentTo(TypeDesc typeDesc) + { + if (base.IsEquivalentTo(typeDesc)) + return true; + + if (typeDesc is EcmaType) + return CheckEquivalence(this, (EcmaType)typeDesc); + + return false; + + static bool CheckEquivalence(EcmaType type1, EcmaType type2) + { + if (!type1.HasCustomAttribute("System.Runtime.InteropServices", "TypeIdentifierAttribute")) + return false; + + if (!type2.HasCustomAttribute("System.Runtime.InteropServices", "TypeIdentifierAttribute")) + return false; + + var attrOpt1 = type1.GetDecodedCustomAttribute("System.Runtime.InteropServices", "TypeIdentifierAttribute"); + var attrOpt2 = type2.GetDecodedCustomAttribute("System.Runtime.InteropServices", "TypeIdentifierAttribute"); + Debug.Assert(attrOpt1.HasValue); + Debug.Assert(attrOpt2.HasValue); + var attr1 = attrOpt1.Value; + var attr2 = attrOpt2.Value; + + if (attr1.FixedArguments.Length != 2 || attr2.FixedArguments.Length != 2) + return false; + + var scope1 = attr1.FixedArguments[0].Value as string; + var identifier1 = attr1.FixedArguments[1].Value as string; + var scope2 = attr2.FixedArguments[0].Value as string; + var identifier2 = attr2.FixedArguments[1].Value as string; + + if (scope1 == null || identifier1 == null || scope2 == null || identifier2 == null) + return false; + + if (identifier1 != identifier2) + return false; + + if (!Guid.TryParse(scope1, out var scopeGuidValue1)) + return false; + + if (!Guid.TryParse(scope2, out var scopeGuidValue2)) + return false; + + if (scopeGuidValue1 != scopeGuidValue2) + return false; + + return true; + } + } } }