Type.IsAssignableTo (#40326)
authorBen Adams <thundercat@illyriad.co.uk>
Tue, 11 Aug 2020 00:09:14 +0000 (01:09 +0100)
committerGitHub <noreply@github.com>
Tue, 11 Aug 2020 00:09:14 +0000 (17:09 -0700)
* IsAssignableTo

* ifdef mono C change

* Add Jit optimization

* Add [Intrinsic] attribute

* Add tests

* More tests

* Add null test

15 files changed:
src/coreclr/src/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs
src/coreclr/src/jit/compiler.h
src/coreclr/src/jit/importer.cpp
src/coreclr/src/jit/namedintrinsiclist.h
src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs
src/libraries/System.Private.CoreLib/src/System/Type.cs
src/libraries/System.Reflection/tests/TypeDerivedTests.cs
src/libraries/System.Reflection/tests/TypeInfoTests.cs
src/libraries/System.Runtime/ref/System.Runtime.cs
src/libraries/System.Runtime/tests/System/Reflection/TypeDelegatorTests.cs
src/mono/mono/metadata/reflection.c
src/mono/netcore/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.Mono.cs
src/tests/JIT/Intrinsics/TypeIntrinsics.IsAssignableTo.cs [new file with mode: 0644]
src/tests/JIT/Intrinsics/TypeIntrinsics_r.csproj
src/tests/JIT/Intrinsics/TypeIntrinsics_ro.csproj

index 7a49fad..1fb5467 100644 (file)
@@ -126,7 +126,7 @@ namespace System.Collections.Generic
                 result = new ByteEqualityComparer();
             }
             // If T implements IEquatable<T> return a GenericEqualityComparer<T>
-            else if (typeof(IEquatable<>).MakeGenericType(type).IsAssignableFrom(type))
+            else if (type.IsAssignableTo(typeof(IEquatable<>).MakeGenericType(type)))
             {
                 result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericEqualityComparer<int>), runtimeType);
             }
index 22cecee..1c6f878 100644 (file)
@@ -3730,6 +3730,7 @@ protected:
 
     void impImportLeave(BasicBlock* block);
     void impResetLeaveBlock(BasicBlock* block, unsigned jmpAddr);
+    GenTree* impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom);
     GenTree* impIntrinsic(GenTree*                newobjThis,
                           CORINFO_CLASS_HANDLE    clsHnd,
                           CORINFO_METHOD_HANDLE   method,
index 463054b..2823851 100644 (file)
@@ -4126,44 +4126,19 @@ GenTree* Compiler::impIntrinsic(GenTree*                newobjThis,
 
             case NI_System_Type_IsAssignableFrom:
             {
-                // Optimize patterns like:
-                //
-                //   typeof(TTo).IsAssignableFrom(typeof(TTFrom))
-                //   valueTypeVar.GetType().IsAssignableFrom(typeof(TTFrom))
-                //
-                // to true/false
                 GenTree* typeTo   = impStackTop(1).val;
                 GenTree* typeFrom = impStackTop(0).val;
 
-                if (typeTo->IsCall() && typeFrom->IsCall())
-                {
-                    // make sure both arguments are `typeof()`
-                    CORINFO_METHOD_HANDLE hTypeof = eeFindHelper(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE);
-                    if ((typeTo->AsCall()->gtCallMethHnd == hTypeof) && (typeFrom->AsCall()->gtCallMethHnd == hTypeof))
-                    {
-                        CORINFO_CLASS_HANDLE hClassTo =
-                            gtGetHelperArgClassHandle(typeTo->AsCall()->gtCallArgs->GetNode());
-                        CORINFO_CLASS_HANDLE hClassFrom =
-                            gtGetHelperArgClassHandle(typeFrom->AsCall()->gtCallArgs->GetNode());
-
-                        if (hClassTo == NO_CLASS_HANDLE || hClassFrom == NO_CLASS_HANDLE)
-                        {
-                            break;
-                        }
+                retNode = impTypeIsAssignable(typeTo, typeFrom);
+                break;
+            }
 
-                        TypeCompareState castResult = info.compCompHnd->compareTypesForCast(hClassFrom, hClassTo);
-                        if (castResult == TypeCompareState::May)
-                        {
-                            // requires runtime check
-                            // e.g. __Canon, COMObjects, Nullable
-                            break;
-                        }
+            case NI_System_Type_IsAssignableTo:
+            {
+                GenTree* typeTo   = impStackTop(0).val;
+                GenTree* typeFrom = impStackTop(1).val;
 
-                        retNode = gtNewIconNode((castResult == TypeCompareState::Must) ? 1 : 0);
-                        impPopStack(); // drop both CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE calls
-                        impPopStack();
-                    }
-                }
+                retNode = impTypeIsAssignable(typeTo, typeFrom);
                 break;
             }
 
@@ -4368,6 +4343,50 @@ GenTree* Compiler::impIntrinsic(GenTree*                newobjThis,
     return retNode;
 }
 
+GenTree* Compiler::impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom)
+{
+    // Optimize patterns like:
+    //
+    //   typeof(TTo).IsAssignableFrom(typeof(TTFrom))
+    //   valueTypeVar.GetType().IsAssignableFrom(typeof(TTFrom))
+    //   typeof(TTFrom).IsAssignableTo(typeof(TTo))
+    //   typeof(TTFrom).IsAssignableTo(valueTypeVar.GetType())
+    //
+    // to true/false
+
+    if (typeTo->IsCall() && typeFrom->IsCall())
+    {
+        // make sure both arguments are `typeof()`
+        CORINFO_METHOD_HANDLE hTypeof = eeFindHelper(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE);
+        if ((typeTo->AsCall()->gtCallMethHnd == hTypeof) && (typeFrom->AsCall()->gtCallMethHnd == hTypeof))
+        {
+            CORINFO_CLASS_HANDLE hClassTo   = gtGetHelperArgClassHandle(typeTo->AsCall()->gtCallArgs->GetNode());
+            CORINFO_CLASS_HANDLE hClassFrom = gtGetHelperArgClassHandle(typeFrom->AsCall()->gtCallArgs->GetNode());
+
+            if (hClassTo == NO_CLASS_HANDLE || hClassFrom == NO_CLASS_HANDLE)
+            {
+                return nullptr;
+            }
+
+            TypeCompareState castResult = info.compCompHnd->compareTypesForCast(hClassFrom, hClassTo);
+            if (castResult == TypeCompareState::May)
+            {
+                // requires runtime check
+                // e.g. __Canon, COMObjects, Nullable
+                return nullptr;
+            }
+
+            GenTreeIntCon* retNode = gtNewIconNode((castResult == TypeCompareState::Must) ? 1 : 0);
+            impPopStack(); // drop both CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE calls
+            impPopStack();
+
+            return retNode;
+        }
+    }
+
+    return nullptr;
+}
+
 GenTree* Compiler::impMathIntrinsic(CORINFO_METHOD_HANDLE method,
                                     CORINFO_SIG_INFO*     sig,
                                     var_types             callType,
@@ -4614,6 +4633,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
             {
                 result = NI_System_Type_IsAssignableFrom;
             }
+            else if (strcmp(methodName, "IsAssignableTo") == 0)
+            {
+                result = NI_System_Type_IsAssignableTo;
+            }
         }
     }
 #if defined(TARGET_XARCH) || defined(TARGET_ARM64)
index bc84f85..8ab264e 100644 (file)
@@ -39,6 +39,7 @@ enum NamedIntrinsic : unsigned short
     NI_System_GC_KeepAlive,
     NI_System_Type_get_IsValueType,
     NI_System_Type_IsAssignableFrom,
+    NI_System_Type_IsAssignableTo,
 
     // These are used by HWIntrinsics but are defined more generally
     // to allow dead code optimization and handle the recursion case
index b6d86ea..84cd674 100644 (file)
@@ -227,8 +227,8 @@ namespace System.Diagnostics
                     bool methodChanged = false;
                     if (declaringType != null && declaringType.IsDefined(typeof(CompilerGeneratedAttribute), inherit: false))
                     {
-                        isAsync = typeof(IAsyncStateMachine).IsAssignableFrom(declaringType);
-                        if (isAsync || typeof(IEnumerator).IsAssignableFrom(declaringType))
+                        isAsync = declaringType.IsAssignableTo(typeof(IAsyncStateMachine));
+                        if (isAsync || declaringType.IsAssignableTo(typeof(IEnumerator)))
                         {
                             methodChanged = TryResolveStateMachineMethod(ref mb, out declaringType);
                         }
index f1e7c35..ca6c780 100644 (file)
@@ -109,6 +109,9 @@ namespace System
         public bool IsPrimitive => IsPrimitiveImpl();
         protected abstract bool IsPrimitiveImpl();
         public bool IsValueType { [Intrinsic] get => IsValueTypeImpl(); }
+
+        [Intrinsic]
+        public bool IsAssignableTo([NotNullWhen(true)] Type? targetType) => targetType?.IsAssignableFrom(this) ?? false;
         protected virtual bool IsValueTypeImpl() => IsSubclassOf(typeof(ValueType));
 
         public virtual bool IsSignatureType => false;
index 5a2be15..310caab 100644 (file)
@@ -25,5 +25,20 @@ namespace System.Reflection.Tests
             Assert.False(testType.IsAssignableFrom(compareType));
             Assert.False(compareType.IsAssignableFrom(testType));
         }
+
+        [Fact]
+        public void IsAssignableTo_NullUnderlyingSystemType()
+        {
+            var testType = new TypeWithNullUnderlyingSystemType();
+            Assert.Null(testType.UnderlyingSystemType);
+            Assert.True(testType.IsAssignableTo(testType));
+
+            Type compareType = typeof(int);
+            Assert.False(testType.IsAssignableTo(compareType));
+            Assert.False(compareType.IsAssignableTo(testType));
+
+            Assert.False(testType.IsAssignableTo(null));
+            Assert.False(typeof(object).IsAssignableTo(null));
+        }
     }
 }
index 3660d5c..e2198b0 100644 (file)
@@ -498,6 +498,7 @@ namespace System.Reflection.Tests
 
             Assert.Equal(expected, implementedInterfaces);
             Assert.All(expected, ti => Assert.True(ti.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())));
+            Assert.All(expected, ti => Assert.True(type.GetTypeInfo().IsAssignableTo(ti.GetTypeInfo())));
         }
 
         public static IEnumerable<object[]> IsInstanceOfType_TestData()
@@ -568,10 +569,13 @@ namespace System.Reflection.Tests
         [InlineData(typeof(uint[]), typeof(int[]), true)]
         [InlineData(typeof(IList<int>), typeof(int[]), true)]
         [InlineData(typeof(IList<uint>), typeof(int[]), true)]
-        public void IsAssignableFrom(Type type, Type c, bool expected)
+        public void IsAssignable(Type type, Type c, bool expected)
         {
             Assert.Equal(expected, type.GetTypeInfo().IsAssignableFrom(c));
             Assert.Equal(expected, type.GetTypeInfo().IsAssignableFrom(c?.GetTypeInfo()));
+
+            Assert.Equal(expected, c?.IsAssignableTo(type) ?? false);
+            Assert.Equal(expected, c?.GetTypeInfo().IsAssignableTo(type.GetTypeInfo()) ?? false);
         }
 
         class G<T, U> where T : U
@@ -581,7 +585,7 @@ namespace System.Reflection.Tests
         static volatile object s_boxedInt32;
 
         [Fact]
-        public void IsAssignableFromNullable()
+        public void IsAssignableNullable()
         {
             Type nubInt = typeof(Nullable<int>);
             Type intType = typeof(int);
@@ -592,6 +596,8 @@ namespace System.Reflection.Tests
             // Nullable<T>  is assignable from  int
             Assert.True(nubInt.IsAssignableFrom(intType));
             Assert.False(intType.IsAssignableFrom(nubInt));
+            Assert.False(nubInt.IsAssignableTo(intType));
+            Assert.True(intType.IsAssignableTo(nubInt));
 
             Type nubOfT = nubInt.GetGenericTypeDefinition();
             Type T = nubOfT.GetTypeInfo().GenericTypeParameters[0];
@@ -601,11 +607,18 @@ namespace System.Reflection.Tests
             Assert.True(objType.IsAssignableFrom(T));
             Assert.True(valTypeType.IsAssignableFrom(T));
 
+            Assert.True(T.IsAssignableTo(T));
+            Assert.True(T.IsAssignableTo(objType));
+            Assert.True(T.IsAssignableTo(valTypeType));
+
             // should be false
             // Nullable<T> is not assignable from T
             Assert.False(nubOfT.IsAssignableFrom(T));
             Assert.False(T.IsAssignableFrom(nubOfT));
 
+            Assert.False(nubOfT.IsAssignableTo(T));
+            Assert.False(T.IsAssignableTo(nubOfT));
+
             // illegal type construction due to T->T?
             Assert.Throws<ArgumentException>(() => typeof(G<,>).MakeGenericType(typeof(int), typeof(int?)));
 
@@ -648,12 +661,17 @@ namespace System.Reflection.Tests
             Assert.True(typeof(IFace[]).IsAssignableFrom(a));
             Assert.True(typeof(IEnumerable<IFace>).IsAssignableFrom(a));
 
+            Assert.True(a.IsAssignableTo(typeof(IFace[])));
+            Assert.True(a.IsAssignableTo(typeof(IEnumerable<IFace>)));
+
             Type a1 = typeof(GG<,>).GetGenericArguments()[0].MakeArrayType();
             Type a2 = typeof(GG<,>).GetGenericArguments()[1].MakeArrayType();
             Assert.True(a2.IsAssignableFrom(a1));
+            Assert.True(a1.IsAssignableTo(a2));
 
             Type ie = typeof(IEnumerable<>).MakeGenericType(typeof(GG<,>).GetGenericArguments()[1]);
             Assert.True(ie.IsAssignableFrom(a1));
+            Assert.True(a1.IsAssignableTo(ie));
         }
 
         public static IEnumerable<object[]> IsEquivilentTo_TestData()
index 8ebce34..6481c43 100644 (file)
@@ -4337,6 +4337,7 @@ namespace System
         public abstract object? InvokeMember(string name, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder? binder, object? target, object?[]? args, System.Reflection.ParameterModifier[]? modifiers, System.Globalization.CultureInfo? culture, string[]? namedParameters);
         protected abstract bool IsArrayImpl();
         public virtual bool IsAssignableFrom([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] System.Type? c) { throw null; }
+        public bool IsAssignableTo([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] System.Type? targetType) { throw null; }
         protected abstract bool IsByRefImpl();
         protected abstract bool IsCOMObjectImpl();
         protected virtual bool IsContextfulImpl() { throw null; }
index d358512..ca6dccd 100644 (file)
@@ -20,6 +20,17 @@ namespace System.Reflection.Tests
         }
 
         [Fact]
+        public void IsAssignableTo()
+        {
+            TypeDelegator td = new TypeDelegator(typeof(int));
+
+            Assert.True(td.IsAssignableTo(typeof(int)));
+            Assert.False(td.IsAssignableTo(typeof(string)));
+            Assert.True(typeof(int).IsAssignableTo(td));
+            Assert.False(typeof(string).IsAssignableTo(td));
+        }
+
+        [Fact]
         public void CreateInstance()
         {
             Assert.Equal(typeof(int[]), Array.CreateInstance(new TypeDelegator(typeof(int)), 100).GetType());
index 09851b3..49c9078 100644 (file)
@@ -3131,7 +3131,11 @@ mono_reflection_call_is_assignable_to (MonoClass *klass, MonoClass *oklass, Mono
        error_init (error);
 
        if (method == NULL) {
+#ifdef ENABLE_NETCORE
+               method = mono_class_get_method_from_name_checked (mono_class_get_type_builder_class (), "IsAssignableToInternal", 1, 0, error);
+#else
                method = mono_class_get_method_from_name_checked (mono_class_get_type_builder_class (), "IsAssignableTo", 1, 0, error);
+#endif         
                mono_error_assert_ok (error);
                g_assert (method);
        }
index 346c559..821b091 100644 (file)
@@ -90,7 +90,7 @@ namespace System.Reflection.Emit
         }
 
         [DynamicDependency(nameof(state))]  // Automatically keeps all previous fields too due to StructLayout
-        [DynamicDependency(nameof(IsAssignableTo))] // Used from reflection.c: mono_reflection_call_is_assignable_to
+        [DynamicDependency(nameof(IsAssignableToInternal))] // Used from reflection.c: mono_reflection_call_is_assignable_to
         internal TypeBuilder(ModuleBuilder mb, TypeAttributes attr, int table_idx)
         {
             this.parent = null;
@@ -107,7 +107,7 @@ namespace System.Reflection.Emit
             Justification = "Linker doesn't analyze ResolveUserType but it's an identity function")]
 
         [DynamicDependency(nameof(state))]  // Automatically keeps all previous fields too due to StructLayout
-        [DynamicDependency(nameof(IsAssignableTo))] // Used from reflection.c: mono_reflection_call_is_assignable_to
+        [DynamicDependency(nameof(IsAssignableToInternal))] // Used from reflection.c: mono_reflection_call_is_assignable_to
         internal TypeBuilder(ModuleBuilder mb, string fullname, TypeAttributes attr, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]Type? parent, Type[]? interfaces, PackingSize packing_size, int type_size, Type? nesting_type)
         {
             int sep_index;
@@ -1718,7 +1718,7 @@ namespace System.Reflection.Emit
         }
 
         // FIXME: "arrays"
-        internal bool IsAssignableTo([NotNullWhen(true)] Type? c)
+        internal bool IsAssignableToInternal([NotNullWhen(true)] Type? c)
         {
             if (c == this)
                 return true;
diff --git a/src/tests/JIT/Intrinsics/TypeIntrinsics.IsAssignableTo.cs b/src/tests/JIT/Intrinsics/TypeIntrinsics.IsAssignableTo.cs
new file mode 100644 (file)
index 0000000..6a7ccc7
--- /dev/null
@@ -0,0 +1,190 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
+
+public partial class Program
+{
+    public static void TestIsAssignableTo()
+    {
+        // Primitive types
+        IsTrue (typeof(void).IsAssignableTo(typeof(void)));
+        IsTrue (typeof(byte).IsAssignableTo(typeof(byte)));
+        IsTrue (typeof(int).IsAssignableTo(typeof(int)));
+        IsTrue (typeof(float).IsAssignableTo(typeof(float)));
+        IsTrue (typeof(double).IsAssignableTo(typeof(double)));
+        IsTrue (typeof(byte*).IsAssignableTo(typeof(byte*)));
+        IsTrue (typeof(byte*).IsAssignableTo(typeof(sbyte*)));
+        IsTrue (typeof(void*).IsAssignableTo(typeof(void*)));
+        IsTrue (typeof(byte**).IsAssignableTo(typeof(byte**)));
+        IsFalse(typeof(sbyte).IsAssignableTo(typeof(byte)));
+        IsFalse(typeof(byte).IsAssignableTo(typeof(sbyte)));
+        IsFalse(typeof(long).IsAssignableTo(typeof(int)));
+        IsFalse(typeof(void).IsAssignableTo(typeof(int)));
+        IsFalse(typeof(long).IsAssignableTo(typeof(void)));
+        IsFalse(typeof(int).IsAssignableTo(typeof(long)));
+        IsFalse(typeof(double).IsAssignableTo(typeof(float)));
+        IsFalse(typeof(float).IsAssignableTo(typeof(double)));
+        IsFalse(typeof(long).IsAssignableTo(typeof(double)));
+        IsFalse(typeof(float).IsAssignableTo(typeof(int)));
+        IsFalse(typeof(ulong*).IsAssignableTo(typeof(sbyte*)));
+        IsFalse(typeof(void*).IsAssignableTo(typeof(sbyte*)));
+        IsFalse(typeof(ulong*).IsAssignableTo(typeof(void*)));
+        IsFalse(typeof(IntPtr).IsAssignableTo(typeof(sbyte*)));
+        IsFalse(typeof(sbyte*).IsAssignableTo(typeof(IntPtr)));
+        IsFalse(typeof(byte*).IsAssignableTo(typeof(byte**)));
+        IsFalse(typeof(byte**).IsAssignableTo(typeof(byte*)));
+
+        // Nullable
+        IsTrue (typeof(int).IsAssignableTo(typeof(int?)));
+        IsTrue (typeof(int?).IsAssignableTo(typeof(int?)));
+        IsTrue (typeof(GenericStruct1<int>?).IsAssignableTo(typeof(GenericStruct1<int>?)));
+        IsTrue (typeof(GenericStruct1<string>?).IsAssignableTo(typeof(GenericStruct1<string>?)));
+        IsTrue (typeof(SimpleEnum_int?).IsAssignableTo(typeof(SimpleEnum_int?)));
+        IsFalse(typeof(int?).IsAssignableTo(typeof(int)));
+        IsFalse(typeof(int?).IsAssignableTo(typeof(uint?)));
+        IsFalse(typeof(uint?).IsAssignableTo(typeof(int?)));
+        IsFalse(typeof(SimpleEnum_int?).IsAssignableTo(typeof(SimpleEnum_uint?)));
+        IsFalse(typeof(SimpleEnum_uint?).IsAssignableTo(typeof(SimpleEnum_int?)));
+        IsFalse(typeof(GenericStruct1<uint>?).IsAssignableTo(typeof(GenericStruct1<int>?)));
+
+        // Enums
+        IsTrue (typeof(SimpleEnum_int).IsAssignableTo(typeof(SimpleEnum_int)));
+        IsTrue (typeof(SimpleEnum_int?).IsAssignableTo(typeof(SimpleEnum_int?)));
+        IsTrue (typeof(SimpleEnum_uint).IsAssignableTo(typeof(SimpleEnum_uint)));
+        IsTrue (typeof(SimpleEnum_byte).IsAssignableTo(typeof(SimpleEnum_byte)));
+        IsTrue (typeof(SimpleEnum_uint).IsAssignableTo(typeof(ValueType)));
+        IsFalse(typeof(SimpleEnum_uint).IsAssignableTo(typeof(SimpleEnum_int)));
+        IsFalse(typeof(SimpleEnum_int).IsAssignableTo(typeof(SimpleEnum_uint)));
+        IsFalse(typeof(SimpleEnum_byte).IsAssignableTo(typeof(SimpleEnum_uint)));
+        IsFalse(typeof(SimpleEnum_uint).IsAssignableTo(typeof(SimpleEnum_byte)));
+        IsFalse(typeof(byte).IsAssignableTo(typeof(SimpleEnum_byte)));
+        IsFalse(typeof(SimpleEnum_byte).IsAssignableTo(typeof(byte)));
+        IsFalse(typeof(int).IsAssignableTo(typeof(SimpleEnum_int)));
+        IsFalse(typeof(SimpleEnum_int).IsAssignableTo(typeof(int)));
+        IsFalse(typeof(float).IsAssignableTo(typeof(SimpleEnum_uint)));
+        IsFalse(typeof(SimpleEnum_uint).IsAssignableTo(typeof(float)));
+        IsFalse(typeof(ValueType).IsAssignableTo(typeof(SimpleEnum_uint)));
+
+        // Covariance/Contravariance 
+        IsTrue (typeof(List<string>).IsAssignableTo(typeof(IEnumerable<object>)));
+        IsTrue (typeof(List<ClassB>).IsAssignableTo(typeof(IEnumerable<ClassA>)));
+        IsTrue (typeof(IList<ClassB>).IsAssignableTo(typeof(IEnumerable<ClassA>)));
+        IsTrue (typeof(IList<ClassD>).IsAssignableTo(typeof(IEnumerable<ClassA>)));
+        IsTrue (typeof(IList<ClassA>).IsAssignableTo(typeof(IEnumerable<ClassA>)));
+        IsTrue (typeof(Action<object>).IsAssignableTo(typeof(Action<string>)));
+        IsTrue (typeof(string[]).IsAssignableTo(typeof(object[])));
+        IsTrue (typeof(string[,]).IsAssignableTo(typeof(object[,])));
+        IsTrue (typeof(SimpleEnum_uint[,]).IsAssignableTo(typeof(SimpleEnum_int[,])));
+        IsFalse(typeof(object[,]).IsAssignableTo(typeof(string[,])));
+        IsFalse(typeof(string[,,]).IsAssignableTo(typeof(object[,])));
+        IsFalse(typeof(IDictionary<ClassB, int>).IsAssignableTo(typeof(IDictionary<ClassA, int>)));
+        IsFalse(typeof(Dictionary<ClassB, int>).IsAssignableTo(typeof(IDictionary<ClassA, int>)));
+        IsFalse(typeof(Action<string>).IsAssignableTo(typeof(Action<object>)));
+        IsFalse(typeof(Action<Guid>).IsAssignableTo(typeof(Action<object>)));
+        IsFalse(typeof(IEnumerable<object>).IsAssignableTo(typeof(List<string>)));
+        IsFalse(typeof(Action<object>).IsAssignableTo(typeof(Action<Guid>)));
+        IsFalse(typeof(IEnumerable<ClassA>).IsAssignableTo(typeof(List<ClassB>)));
+        IsFalse(typeof(IEnumerable<ClassA>).IsAssignableTo(typeof(IList<ClassB>)));
+        IsFalse(typeof(IEnumerable<ClassA>).IsAssignableTo(typeof(IList<ClassD>)));
+        IsFalse(typeof(IEnumerable<ClassA>).IsAssignableTo(typeof(IList<ClassA>)));
+
+        // Arrays
+        IsTrue(typeof(sbyte[]).IsAssignableTo(typeof(byte[])));
+        IsTrue(typeof(byte[]).IsAssignableTo(typeof(sbyte[])));
+        IsTrue(typeof(ushort[]).IsAssignableTo(typeof(short[])));
+        IsTrue(typeof(short[]).IsAssignableTo(typeof(ushort[])));
+        IsTrue(typeof(uint[]).IsAssignableTo(typeof(int[])));
+        IsTrue(typeof(int[]).IsAssignableTo(typeof(uint[])));
+        IsTrue(typeof(ulong[]).IsAssignableTo(typeof(long[])));
+        IsTrue(typeof(long[]).IsAssignableTo(typeof(ulong[])));
+        IsTrue(typeof(ulong[,]).IsAssignableTo(typeof(long[,])));
+        IsTrue(typeof(long[,,]).IsAssignableTo(typeof(ulong[,,])));
+        IsTrue(typeof(Struct1[]).IsAssignableTo(typeof(Struct1[])));
+        IsFalse(typeof(byte[]).IsAssignableTo(typeof(int[])));
+        IsFalse(typeof(sbyte[]).IsAssignableTo(typeof(int[])));
+        IsFalse(typeof(short[]).IsAssignableTo(typeof(int[])));
+        IsFalse(typeof(ushort[]).IsAssignableTo(typeof(int[])));
+        IsFalse(typeof(float[]).IsAssignableTo(typeof(int[])));
+        IsFalse(typeof(double[]).IsAssignableTo(typeof(int[])));
+        IsFalse(typeof(double[]).IsAssignableTo(typeof(long[])));
+        IsFalse(typeof(Struct2[]).IsAssignableTo(typeof(Struct1[])));
+        IsFalse(typeof(GenericStruct1<int>[]).IsAssignableTo(typeof(Struct1[])));
+        IsFalse(typeof(GenericStruct1<int>[]).IsAssignableTo(typeof(GenericStruct1<uint>[])));
+
+        // Misc
+        IsTrue (typeof(byte).IsAssignableTo(typeof(object)));
+        IsTrue (typeof(int).IsAssignableTo(typeof(object)));
+        IsTrue (typeof(float).IsAssignableTo(typeof(object)));
+        IsTrue (typeof(SimpleEnum_uint).IsAssignableTo(typeof(object)));
+        IsTrue (typeof(IDisposable).IsAssignableTo(typeof(object)));
+        IsTrue (typeof(IDictionary<string, string>).IsAssignableTo(typeof(object)));
+        IsTrue (typeof(List<int>).IsAssignableTo(typeof(object)));
+        IsTrue (typeof(List<>).IsAssignableTo(typeof(object)));
+        IsTrue (typeof(Action<>).IsAssignableTo(typeof(object)));
+        IsTrue (typeof(Action<int>).IsAssignableTo(typeof(object)));
+        IsTrue (typeof(Vector128<float>).IsAssignableTo(typeof(object)));
+        IsTrue (typeof(Vector256<int>).IsAssignableTo(typeof(object)));
+        IsTrue (typeof(ClassA).IsAssignableTo(typeof(ClassA)));
+        IsTrue (typeof(ClassB).IsAssignableTo(typeof(ClassA)));
+        IsTrue (typeof(ClassC).IsAssignableTo(typeof(ClassA)));
+        IsTrue (typeof(decimal).IsAssignableTo(typeof(decimal)));
+        IsTrue (typeof(Struct1).IsAssignableTo(typeof(Struct1)));
+        IsTrue (typeof(Struct3).IsAssignableTo(typeof(IDisposable)));
+        IsTrue (typeof(Dictionary<,>).IsAssignableTo(typeof(Dictionary<,>)));
+        IsTrue (typeof(IDictionary<,>).IsAssignableTo(typeof(IDictionary<,>)));
+        IsTrue (typeof(GenericStruct1<>).IsAssignableTo(typeof(GenericStruct1<>)));
+        IsTrue (typeof(GenericStruct1<int>).IsAssignableTo(typeof(GenericStruct1<int>)));
+        IsTrue (typeof(GenericStruct1<string>).IsAssignableTo(typeof(GenericStruct1<string>)));
+        IsFalse(typeof(IDisposable).IsAssignableTo(typeof(byte)));
+        IsFalse(typeof(IEnumerable).IsAssignableTo(typeof(IDisposable)));
+        IsFalse(typeof(IDictionary<string, int>).IsAssignableTo(typeof(IDictionary<string, string>)));
+        IsFalse(typeof(IList<int>).IsAssignableTo(typeof(List<int>)));
+        IsFalse(typeof(List<IDisposable>).IsAssignableTo(typeof(List<>)));
+        IsFalse(typeof(Action<int>).IsAssignableTo(typeof(Action<>)));
+        IsFalse(typeof(Func<int>).IsAssignableTo(typeof(Action<>)));
+        IsFalse(typeof(CustomAction).IsAssignableTo(typeof(Action)));
+        IsFalse(typeof(void).IsAssignableTo(typeof(Action<int>)));
+        IsFalse(typeof(ClassD).IsAssignableTo(typeof(ClassB)));
+        IsFalse(typeof(Dictionary<int,int>).IsAssignableTo(typeof(Dictionary<,>)));
+        IsFalse(typeof(GenericStruct1<ClassB>).IsAssignableTo(typeof(GenericStruct1<ClassA>)));
+        IsFalse(typeof(Struct2).IsAssignableTo(typeof(Struct1)));
+        IsFalse(typeof(GenericStruct2<>).IsAssignableTo(typeof(GenericStruct1<>)));
+        IsFalse(typeof(GenericStruct2<int>).IsAssignableTo(typeof(GenericStruct1<int>)));
+        IsFalse(typeof(byte*).IsAssignableTo(typeof(object)));
+        IsFalse(typeof(byte**).IsAssignableTo(typeof(object)));
+        IsFalse(typeof(Vector128<float>).IsAssignableTo(typeof(Vector128<double>)));
+        IsFalse(typeof(Vector128<int>).IsAssignableTo(typeof(Vector128<float>)));
+        IsFalse(typeof(Vector128<float>).IsAssignableTo(typeof(Vector128<int>)));
+        IsFalse(typeof(Vector128<float>).IsAssignableTo(typeof(Vector4)));
+        IsFalse(typeof(Vector4).IsAssignableTo(typeof(Vector128<float>)));
+        IsFalse(typeof(Vector<float>).IsAssignableTo(typeof(Vector128<float>)));
+        IsFalse(typeof(Vector<float>).IsAssignableTo(typeof(Vector256<float>)));
+
+        // System.__Canon
+        IsTrue (IsAssignableTo<KeyValuePair<IDisposable, IDisposable>, KeyValuePair<IDisposable, IDisposable>>());
+        IsTrue (IsAssignableTo<KeyValuePair<IDisposable, object>, KeyValuePair<IDisposable, object>>());
+        IsTrue (IsAssignableTo<IDictionary<IDisposable, IDisposable>, IDictionary<IDisposable, IDisposable>>());
+        IsTrue (IsAssignableTo<IDictionary<IDisposable, object>, IDictionary<IDisposable, object>>());
+        IsTrue (IsAssignableTo<Dictionary<IDisposable, IDisposable>, Dictionary<IDisposable, IDisposable>>());
+        IsTrue (IsAssignableTo<Dictionary<IDisposable, object>, Dictionary<IDisposable, object>>());
+        IsTrue (IsAssignableTo<KeyValuePair<int, int>, KeyValuePair<int, int>>());
+        IsTrue (IsAssignableTo<KeyValuePair<IEnumerable<int>, IEnumerable<int>>, KeyValuePair<IEnumerable<int>, IEnumerable<int>>>());
+        IsFalse(IsAssignableTo<KeyValuePair<IDisposable, object>, KeyValuePair<IDisposable, IDisposable>>());
+        IsFalse(IsAssignableTo<KeyValuePair<IDisposable, object>, KeyValuePair<IDisposable, int>>());
+        IsFalse(IsAssignableTo<IDictionary<IDisposable, object>, IDictionary<IDisposable, IDisposable>>());
+        IsFalse(IsAssignableTo<IDictionary<IDisposable, object>, IDictionary<IDisposable, int>>());
+        IsFalse(IsAssignableTo<Dictionary<IDisposable, object>, Dictionary<IDisposable, IDisposable>>());
+        IsFalse(IsAssignableTo<Dictionary<IDisposable, object>, Dictionary<IDisposable, int>>());
+        IsFalse(IsAssignableTo<KeyValuePair<int, object>, KeyValuePair<int, int>>());
+        IsFalse(IsAssignableTo<KeyValuePair<IEnumerable<int>, IEnumerable<uint>>, KeyValuePair<IEnumerable<int>, IEnumerable<int>>>());
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static bool IsAssignableTo<TTFrom, TTo>() => typeof(TTFrom).IsAssignableTo(typeof(TTo));
+}
index 4e2edb2..9ee21d6 100644 (file)
@@ -7,5 +7,6 @@
   <ItemGroup>
     <Compile Include="TypeIntrinsics.cs" />
     <Compile Include="TypeIntrinsics.IsAssignableFrom.cs" />
+    <Compile Include="TypeIntrinsics.IsAssignableTo.cs" />
   </ItemGroup>
 </Project>
index 2e35b52..d752cdd 100644 (file)
@@ -7,5 +7,6 @@
   <ItemGroup>
     <Compile Include="TypeIntrinsics.cs" />
     <Compile Include="TypeIntrinsics.IsAssignableFrom.cs" />
+    <Compile Include="TypeIntrinsics.IsAssignableTo.cs" />
   </ItemGroup>
 </Project>