Properly override IsEquivalentTo() API in RuntimeType (#23137)
authorAaron Robinson <arobins@microsoft.com>
Sat, 9 Mar 2019 01:36:37 +0000 (17:36 -0800)
committerGitHub <noreply@github.com>
Sat, 9 Mar 2019 01:36:37 +0000 (17:36 -0800)
* Properly override IsEquivalentTo() API in RuntimeType
* Add tests for API validation

src/System.Private.CoreLib/src/System/RtType.cs
src/System.Private.CoreLib/src/System/RuntimeHandles.cs
src/vm/ecalllist.h
src/vm/runtimehandles.cpp
src/vm/runtimehandles.h
tests/src/baseservices/typeequivalence/simple/Simple.cs

index 2a71d2e..3d03771 100644 (file)
@@ -3225,6 +3225,25 @@ namespace System
             return false;
         }
 
+#if FEATURE_TYPEEQUIVALENCE
+        // Reflexive, symmetric, transitive.
+        public override bool IsEquivalentTo(Type other)
+        {
+            var otherRtType = other as RuntimeType;
+            if (otherRtType is null)
+            {
+                return false;
+            }
+
+            if (otherRtType == this)
+            {
+                return true;
+            }
+
+            return RuntimeTypeHandle.IsEquivalentTo(this, otherRtType);
+        }
+#endif // FEATURE_TYPEEQUIVALENCE
+
         public override Type BaseType => GetBaseType();
 
         private RuntimeType GetBaseType()
index 1325aa8..56694c6 100644 (file)
@@ -615,6 +615,11 @@ namespace System
         {
             throw new PlatformNotSupportedException();
         }
+
+#if FEATURE_TYPEEQUIVALENCE
+        [MethodImplAttribute(MethodImplOptions.InternalCall)]
+        internal static extern bool IsEquivalentTo(RuntimeType rtType1, RuntimeType rtType2);
+#endif // FEATURE_TYPEEQUIVALENCE
     }
 
     // This type is used to remove the expense of having a managed reference object that is dynamically 
index 35fbae2..0298939 100644 (file)
@@ -268,6 +268,7 @@ FCFuncStart(gCOMTypeHandleFuncs)
     FCFuncElement("Allocate", RuntimeTypeHandle::Allocate) //for A.CI
     FCFuncElement("CompareCanonicalHandles", RuntimeTypeHandle::CompareCanonicalHandles)
     FCIntrinsic("GetValueInternal", RuntimeTypeHandle::GetValueInternal, CORINFO_INTRINSIC_RTH_GetValueInternal)
+    FCFuncElement("IsEquivalentTo", RuntimeTypeHandle::IsEquivalentTo)
 FCFuncEnd()
 
 FCFuncStart(gMetaDataImport)
index dda5d1d..c8782db 100644 (file)
@@ -250,6 +250,25 @@ FCIMPL1_V(EnregisteredTypeHandle, RuntimeTypeHandle::GetValueInternal, FCALLRunt
 }
 FCIMPLEND
 
+FCIMPL2(FC_BOOL_RET, RuntimeTypeHandle::IsEquivalentTo, ReflectClassBaseObject *rtType1UNSAFE, ReflectClassBaseObject *rtType2UNSAFE)
+{
+    FCALL_CONTRACT;
+
+    REFLECTCLASSBASEREF rtType1 = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(rtType1UNSAFE);
+    REFLECTCLASSBASEREF rtType2 = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(rtType2UNSAFE);
+
+    BOOL areEquivalent = FALSE;
+    HELPER_METHOD_FRAME_BEGIN_RET_2(rtType1, rtType2);
+
+    if (rtType1 != NULL && rtType2 != NULL)
+        areEquivalent = rtType1->GetType().IsEquivalentTo(rtType2->GetType());
+
+    HELPER_METHOD_FRAME_END();
+
+    FC_RETURN_BOOL(areEquivalent);
+}
+FCIMPLEND
+
 // TypeEqualsHelper and TypeNotEqualsHelper are almost identical.
 // Unfortunately we cannot combime them because they need to hardcode the caller's name
 NOINLINE static BOOL TypeEqualSlow(OBJECTREF refL, OBJECTREF refR, LPVOID __me)
index 6edf61e..66a27f4 100644 (file)
@@ -152,6 +152,8 @@ public:
     static FCDECL1_V(ReflectClassBaseObject*, GetTypeFromHandle, FCALLRuntimeTypeHandle th);
     static FCDECL1_V(EnregisteredTypeHandle, GetValueInternal, FCALLRuntimeTypeHandle RTH);
 
+    static FCDECL2(FC_BOOL_RET, IsEquivalentTo, ReflectClassBaseObject *rtType1UNSAFE, ReflectClassBaseObject *rtType2UNSAFE);
+
     static FCDECL2(FC_BOOL_RET, TypeEQ, Object* left, Object* right);
     static FCDECL2(FC_BOOL_RET, TypeNEQ, Object* left, Object* right);
 
index 0b6a54d..56c3ecd 100644 (file)
@@ -38,6 +38,34 @@ public class Simple
         }
     }
 
+    private static void ValidateTypeInstanceEquality()
+    {
+        Console.WriteLine($"{nameof(ValidateTypeInstanceEquality)}");
+        var inAsm = EmptyType.Create();
+        var otherAsm = EmptyType2.Create();
+
+        Type inAsmInterfaceType = inAsm.GetType().GetInterface(nameof(IEmptyType));
+        Type otherAsmInterfaceType = otherAsm.GetType().GetInterface(nameof(IEmptyType));
+
+        // Sanity checks
+        Assert.IsTrue(inAsmInterfaceType == inAsmInterfaceType);
+        Assert.IsTrue(inAsmInterfaceType.IsEquivalentTo(inAsmInterfaceType));
+        Assert.IsFalse(inAsmInterfaceType.IsEquivalentTo(inAsm.GetType()));
+        Assert.IsTrue(otherAsmInterfaceType == otherAsmInterfaceType);
+        Assert.IsTrue(otherAsmInterfaceType.IsEquivalentTo(otherAsmInterfaceType));
+        Assert.IsFalse(otherAsmInterfaceType.IsEquivalentTo(otherAsm.GetType()));
+
+        // The intrinsic equality operations should fail
+        Assert.IsFalse(inAsmInterfaceType == otherAsmInterfaceType);
+        Assert.IsFalse(inAsmInterfaceType.Equals(otherAsmInterfaceType));
+        Assert.IsFalse(otherAsmInterfaceType == inAsmInterfaceType);
+        Assert.IsFalse(otherAsmInterfaceType.Equals(inAsmInterfaceType));
+
+        // Determination of equal types requires API call
+        Assert.IsTrue(inAsmInterfaceType.IsEquivalentTo(otherAsmInterfaceType));
+        Assert.IsTrue(otherAsmInterfaceType.IsEquivalentTo(inAsmInterfaceType));
+    }
+
     private class MethodTestDerived : MethodTestBase
     {
         private readonly int scaleValue;
@@ -127,6 +155,7 @@ public class Simple
         try
         {
             InterfaceTypesFromDifferentAssembliesAreEquivalent();
+            ValidateTypeInstanceEquality();
             InterfaceTypesMethodOperations();
             CallSparseInterface();
         }
@@ -138,4 +167,4 @@ public class Simple
 
         return 100;
     }
-}
\ No newline at end of file
+}