From 59287c178d1c959fe570495200a75a9c310ae8fa Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 5 Aug 2020 04:01:18 +0100 Subject: [PATCH 1/9] IsAssignableTo --- .../src/System/Collections/Generic/ComparerHelpers.cs | 2 +- .../src/System/Diagnostics/StackTrace.cs | 4 ++-- src/libraries/System.Private.CoreLib/src/System/Type.cs | 1 + src/libraries/System.Runtime/ref/System.Runtime.cs | 1 + src/mono/mono/metadata/reflection.c | 2 +- .../src/System/Reflection/Emit/TypeBuilder.Mono.cs | 6 +++--- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs index 7a49fad0213371..1fb54677d0c5a3 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs @@ -126,7 +126,7 @@ internal static object CreateDefaultEqualityComparer(Type type) result = new ByteEqualityComparer(); } // If T implements IEquatable return a GenericEqualityComparer - else if (typeof(IEquatable<>).MakeGenericType(type).IsAssignableFrom(type)) + else if (type.IsAssignableTo(typeof(IEquatable<>).MakeGenericType(type))) { result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericEqualityComparer), runtimeType); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs index b6d86eac806c15..84cd6745f306ab 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs @@ -227,8 +227,8 @@ internal void ToString(TraceFormat traceFormat, StringBuilder sb) 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); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Type.cs b/src/libraries/System.Private.CoreLib/src/System/Type.cs index f1e7c35b754c7e..b2f4d7c35b7741 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Type.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Type.cs @@ -109,6 +109,7 @@ public virtual Type[] GetGenericParameterConstraints() public bool IsPrimitive => IsPrimitiveImpl(); protected abstract bool IsPrimitiveImpl(); public bool IsValueType { [Intrinsic] get => IsValueTypeImpl(); } + public bool IsAssignableTo([NotNullWhen(true)] Type? targetType) => targetType?.IsAssignableFrom(this) ?? false; protected virtual bool IsValueTypeImpl() => IsSubclassOf(typeof(ValueType)); public virtual bool IsSignatureType => false; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 8ebce34c3dbf8d..6481c43f380832 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -4337,6 +4337,7 @@ protected Type() { } 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; } diff --git a/src/mono/mono/metadata/reflection.c b/src/mono/mono/metadata/reflection.c index 09851b355a1f45..8e7a84f5d02fcf 100644 --- a/src/mono/mono/metadata/reflection.c +++ b/src/mono/mono/metadata/reflection.c @@ -3131,7 +3131,7 @@ mono_reflection_call_is_assignable_to (MonoClass *klass, MonoClass *oklass, Mono error_init (error); if (method == NULL) { - method = mono_class_get_method_from_name_checked (mono_class_get_type_builder_class (), "IsAssignableTo", 1, 0, error); + method = mono_class_get_method_from_name_checked (mono_class_get_type_builder_class (), "IsAssignableToInternal", 1, 0, error); mono_error_assert_ok (error); g_assert (method); } diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.Mono.cs index 346c559144982a..821b091b68ed72 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.Mono.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.Mono.cs @@ -90,7 +90,7 @@ protected override TypeAttributes GetAttributeFlagsImpl() } [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 @@ internal TypeBuilder(ModuleBuilder mb, TypeAttributes attr, int table_idx) 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 @@ public override bool IsAssignableFrom([NotNullWhen(true)] Type? c) } // FIXME: "arrays" - internal bool IsAssignableTo([NotNullWhen(true)] Type? c) + internal bool IsAssignableToInternal([NotNullWhen(true)] Type? c) { if (c == this) return true; From e4178362626f33f7dda4cb423d8aa5802ba9b342 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 7 Aug 2020 14:17:01 +0100 Subject: [PATCH 2/9] ifdef mono C change --- src/mono/mono/metadata/reflection.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mono/mono/metadata/reflection.c b/src/mono/mono/metadata/reflection.c index 8e7a84f5d02fcf..49c907829eb715 100644 --- a/src/mono/mono/metadata/reflection.c +++ b/src/mono/mono/metadata/reflection.c @@ -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); } From 4389f2a62ac8f831eb10231d56e1693c05718579 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 10 Aug 2020 12:31:38 +0100 Subject: [PATCH 3/9] Add Jit optimization --- src/coreclr/src/jit/compiler.h | 1 + src/coreclr/src/jit/importer.cpp | 93 ++++++++++++++++-------- src/coreclr/src/jit/namedintrinsiclist.h | 1 + 3 files changed, 64 insertions(+), 31 deletions(-) diff --git a/src/coreclr/src/jit/compiler.h b/src/coreclr/src/jit/compiler.h index 22ceceea5ae075..44b42dfe9a290f 100644 --- a/src/coreclr/src/jit/compiler.h +++ b/src/coreclr/src/jit/compiler.h @@ -3730,6 +3730,7 @@ class Compiler void impImportLeave(BasicBlock* block); void impResetLeaveBlock(BasicBlock* block, unsigned jmpAddr); + GenTreeIntCon* impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom); GenTree* impIntrinsic(GenTree* newobjThis, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, diff --git a/src/coreclr/src/jit/importer.cpp b/src/coreclr/src/jit/importer.cpp index 463054b9b989ad..6919ec5cdcc55a 100644 --- a/src/coreclr/src/jit/importer.cpp +++ b/src/coreclr/src/jit/importer.cpp @@ -4126,43 +4126,26 @@ 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()) + GenTreeIntCon* node = impTypeIsAssignable(typeTo, typeFrom); + if (node != nullptr) { - // 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 = node; + } + 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(); - } + GenTreeIntCon* node = impTypeIsAssignable(typeTo, typeFrom); + if (node != nullptr) + { + retNode = node; } break; } @@ -4368,6 +4351,50 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, return retNode; } +GenTreeIntCon* 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 +4641,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) diff --git a/src/coreclr/src/jit/namedintrinsiclist.h b/src/coreclr/src/jit/namedintrinsiclist.h index bc84f851deac8a..8ab264e36061c4 100644 --- a/src/coreclr/src/jit/namedintrinsiclist.h +++ b/src/coreclr/src/jit/namedintrinsiclist.h @@ -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 From a9ee6cfcebeb0ab24d8849da8c34bf4ef345e0a6 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 10 Aug 2020 13:29:59 +0100 Subject: [PATCH 4/9] Add [Intrinsic] attribute --- src/libraries/System.Private.CoreLib/src/System/Type.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Type.cs b/src/libraries/System.Private.CoreLib/src/System/Type.cs index b2f4d7c35b7741..ca6c780e136c4b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Type.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Type.cs @@ -109,6 +109,8 @@ public virtual Type[] GetGenericParameterConstraints() 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)); From acfe0724aca0269b77592a71e186a43f73c305d0 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 10 Aug 2020 15:10:38 +0100 Subject: [PATCH 5/9] Feedback --- src/coreclr/src/jit/compiler.h | 2 +- src/coreclr/src/jit/importer.cpp | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/coreclr/src/jit/compiler.h b/src/coreclr/src/jit/compiler.h index 44b42dfe9a290f..1c6f87851f52fe 100644 --- a/src/coreclr/src/jit/compiler.h +++ b/src/coreclr/src/jit/compiler.h @@ -3730,7 +3730,7 @@ class Compiler void impImportLeave(BasicBlock* block); void impResetLeaveBlock(BasicBlock* block, unsigned jmpAddr); - GenTreeIntCon* impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom); + GenTree* impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom); GenTree* impIntrinsic(GenTree* newobjThis, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, diff --git a/src/coreclr/src/jit/importer.cpp b/src/coreclr/src/jit/importer.cpp index 6919ec5cdcc55a..2823851cd094c6 100644 --- a/src/coreclr/src/jit/importer.cpp +++ b/src/coreclr/src/jit/importer.cpp @@ -4129,11 +4129,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, GenTree* typeTo = impStackTop(1).val; GenTree* typeFrom = impStackTop(0).val; - GenTreeIntCon* node = impTypeIsAssignable(typeTo, typeFrom); - if (node != nullptr) - { - retNode = node; - } + retNode = impTypeIsAssignable(typeTo, typeFrom); break; } @@ -4142,11 +4138,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, GenTree* typeTo = impStackTop(0).val; GenTree* typeFrom = impStackTop(1).val; - GenTreeIntCon* node = impTypeIsAssignable(typeTo, typeFrom); - if (node != nullptr) - { - retNode = node; - } + retNode = impTypeIsAssignable(typeTo, typeFrom); break; } @@ -4351,7 +4343,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, return retNode; } -GenTreeIntCon* Compiler::impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom) +GenTree* Compiler::impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom) { // Optimize patterns like: // From 29780bfc01a72ba2f2f26d58c599715e4a48bd06 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 10 Aug 2020 16:23:24 +0100 Subject: [PATCH 6/9] Add tests --- .../TypeIntrinsics.IsAssignableTo.cs | 190 ++++++++++++++++++ .../JIT/Intrinsics/TypeIntrinsics_r.csproj | 1 + .../JIT/Intrinsics/TypeIntrinsics_ro.csproj | 1 + 3 files changed, 192 insertions(+) create mode 100644 src/tests/JIT/Intrinsics/TypeIntrinsics.IsAssignableTo.cs diff --git a/src/tests/JIT/Intrinsics/TypeIntrinsics.IsAssignableTo.cs b/src/tests/JIT/Intrinsics/TypeIntrinsics.IsAssignableTo.cs new file mode 100644 index 00000000000000..4f1b1b59e3759a --- /dev/null +++ b/src/tests/JIT/Intrinsics/TypeIntrinsics.IsAssignableTo.cs @@ -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?).IsAssignableTo(typeof(GenericStruct1?))); + IsTrue (typeof(GenericStruct1?).IsAssignableTo(typeof(GenericStruct1?))); + 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?).IsAssignableTo(typeof(GenericStruct1?))); + + // 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).IsAssignableTo(typeof(IEnumerable))); + IsTrue (typeof(List).IsAssignableTo(typeof(IEnumerable))); + IsTrue (typeof(IList).IsAssignableTo(typeof(IEnumerable))); + IsTrue (typeof(IList).IsAssignableTo(typeof(IEnumerable))); + IsTrue (typeof(IList).IsAssignableTo(typeof(IEnumerable))); + IsTrue (typeof(Action).IsAssignableTo(typeof(Action))); + 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).IsAssignableTo(typeof(IDictionary))); + IsFalse(typeof(Dictionary).IsAssignableTo(typeof(IDictionary))); + IsFalse(typeof(Action).IsAssignableTo(typeof(Action))); + IsFalse(typeof(Action).IsAssignableTo(typeof(Action))); + IsFalse(typeof(IEnumerable).IsAssignableTo(typeof(List))); + IsFalse(typeof(Action).IsAssignableTo(typeof(Action))); + IsFalse(typeof(IEnumerable).IsAssignableTo(typeof(List))); + IsFalse(typeof(IEnumerable).IsAssignableTo(typeof(IList))); + IsFalse(typeof(IEnumerable).IsAssignableTo(typeof(IList))); + IsFalse(typeof(IEnumerable).IsAssignableTo(typeof(IList))); + + // 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[]).IsAssignableTo(typeof(Struct1[]))); + IsFalse(typeof(GenericStruct1[]).IsAssignableTo(typeof(GenericStruct1[]))); + + // 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).IsAssignableTo(typeof(object))); + IsTrue (typeof(List).IsAssignableTo(typeof(object))); + IsTrue (typeof(List<>).IsAssignableTo(typeof(object))); + IsTrue (typeof(Action<>).IsAssignableTo(typeof(object))); + IsTrue (typeof(Action).IsAssignableTo(typeof(object))); + IsTrue (typeof(Vector128).IsAssignableTo(typeof(object))); + IsTrue (typeof(Vector256).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).IsAssignableTo(typeof(GenericStruct1))); + IsTrue (typeof(GenericStruct1).IsAssignableTo(typeof(GenericStruct1))); + IsFalse(typeof(IDisposable).IsAssignableTo(typeof(byte))); + IsFalse(typeof(IEnumerable).IsAssignableTo(typeof(IDisposable))); + IsFalse(typeof(IDictionary).IsAssignableTo(typeof(IDictionary))); + IsFalse(typeof(IList).IsAssignableTo(typeof(List))); + IsFalse(typeof(List).IsAssignableTo(typeof(List<>))); + IsFalse(typeof(Action).IsAssignableTo(typeof(Action<>))); + IsFalse(typeof(Func).IsAssignableTo(typeof(Action<>))); + IsFalse(typeof(CustomAction).IsAssignableTo(typeof(Action))); + IsFalse(typeof(void).IsAssignableTo(typeof(Action))); + IsFalse(typeof(ClassD).IsAssignableTo(typeof(ClassB))); + IsFalse(typeof(Dictionary).IsAssignableTo(typeof(Dictionary<,>))); + IsFalse(typeof(GenericStruct1).IsAssignableTo(typeof(GenericStruct1))); + IsFalse(typeof(Struct2).IsAssignableTo(typeof(Struct1))); + IsFalse(typeof(GenericStruct2<>).IsAssignableTo(typeof(GenericStruct1<>))); + IsFalse(typeof(GenericStruct2).IsAssignableTo(typeof(GenericStruct1))); + IsFalse(typeof(byte*).IsAssignableTo(typeof(object))); + IsFalse(typeof(byte**).IsAssignableTo(typeof(object))); + IsFalse(typeof(Vector128).IsAssignableTo(typeof(Vector128))); + IsFalse(typeof(Vector128).IsAssignableTo(typeof(Vector128))); + IsFalse(typeof(Vector128).IsAssignableTo(typeof(Vector128))); + IsFalse(typeof(Vector128).IsAssignableTo(typeof(Vector4))); + IsFalse(typeof(Vector4).IsAssignableTo(typeof(Vector128))); + IsFalse(typeof(Vector).IsAssignableTo(typeof(Vector128))); + IsFalse(typeof(Vector).IsAssignableTo(typeof(Vector256))); + + // System.__Canon + IsTrue (IsAssignableTo, KeyValuePair>()); + IsTrue (IsAssignableTo, KeyValuePair>()); + IsTrue (IsAssignableTo, IDictionary>()); + IsTrue (IsAssignableTo, IDictionary>()); + IsTrue (IsAssignableTo, Dictionary>()); + IsTrue (IsAssignableTo, Dictionary>()); + IsTrue (IsAssignableTo, KeyValuePair>()); + IsTrue (IsAssignableTo, IEnumerable>, KeyValuePair, IEnumerable>>()); + IsFalse(IsAssignableTo, KeyValuePair>()); + IsFalse(IsAssignableTo, KeyValuePair>()); + IsFalse(IsAssignableTo, IDictionary>()); + IsFalse(IsAssignableTo, IDictionary>()); + IsFalse(IsAssignableTo, Dictionary>()); + IsFalse(IsAssignableTo, Dictionary>()); + IsFalse(IsAssignableTo, KeyValuePair>()); + IsFalse(IsAssignableTo, IEnumerable>, IEnumerable, IEnumerable>>()); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool IsAssignableTo() => typeof(TTFrom).IsAssignableTo(typeof(TTo)); +} diff --git a/src/tests/JIT/Intrinsics/TypeIntrinsics_r.csproj b/src/tests/JIT/Intrinsics/TypeIntrinsics_r.csproj index 4e2edb234c81e0..9ee21d6e902b96 100644 --- a/src/tests/JIT/Intrinsics/TypeIntrinsics_r.csproj +++ b/src/tests/JIT/Intrinsics/TypeIntrinsics_r.csproj @@ -7,5 +7,6 @@ + diff --git a/src/tests/JIT/Intrinsics/TypeIntrinsics_ro.csproj b/src/tests/JIT/Intrinsics/TypeIntrinsics_ro.csproj index 2e35b529e622a2..d752cddd2a06ee 100644 --- a/src/tests/JIT/Intrinsics/TypeIntrinsics_ro.csproj +++ b/src/tests/JIT/Intrinsics/TypeIntrinsics_ro.csproj @@ -7,5 +7,6 @@ + From 495c3dbeaabd5ec511936ddb2213059aeb6e9cd8 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 10 Aug 2020 17:10:28 +0100 Subject: [PATCH 7/9] More tests --- .../tests/TypeDerivedTests.cs | 12 ++++++++++ .../System.Reflection/tests/TypeInfoTests.cs | 22 +++++++++++++++++-- .../System/Reflection/TypeDelegatorTests.cs | 11 ++++++++++ .../TypeIntrinsics.IsAssignableTo.cs | 2 +- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Reflection/tests/TypeDerivedTests.cs b/src/libraries/System.Reflection/tests/TypeDerivedTests.cs index 5a2be15d5ef104..9ffda7c168742d 100644 --- a/src/libraries/System.Reflection/tests/TypeDerivedTests.cs +++ b/src/libraries/System.Reflection/tests/TypeDerivedTests.cs @@ -25,5 +25,17 @@ public void IsAssignableFrom_NullUnderlyingSystemType() Assert.False(testType.IsAssignableFrom(compareType)); Assert.False(compareType.IsAssignableFrom(testType)); } + + [Fact] + public void IsAssignableFrom_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)); + } } } diff --git a/src/libraries/System.Reflection/tests/TypeInfoTests.cs b/src/libraries/System.Reflection/tests/TypeInfoTests.cs index 3660d5cb5beb77..e2198b09214044 100644 --- a/src/libraries/System.Reflection/tests/TypeInfoTests.cs +++ b/src/libraries/System.Reflection/tests/TypeInfoTests.cs @@ -498,6 +498,7 @@ public void ImplementedInterfaces(Type type, Type[] expected) 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 IsInstanceOfType_TestData() @@ -568,10 +569,13 @@ public void IsInstanceOfType(Type type, object o, bool expected) [InlineData(typeof(uint[]), typeof(int[]), true)] [InlineData(typeof(IList), typeof(int[]), true)] [InlineData(typeof(IList), 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 where T : U @@ -581,7 +585,7 @@ class G where T : U static volatile object s_boxedInt32; [Fact] - public void IsAssignableFromNullable() + public void IsAssignableNullable() { Type nubInt = typeof(Nullable); Type intType = typeof(int); @@ -592,6 +596,8 @@ public void IsAssignableFromNullable() // Nullable 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 @@ public void IsAssignableFromNullable() 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 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(() => typeof(G<,>).MakeGenericType(typeof(int), typeof(int?))); @@ -648,12 +661,17 @@ public void OpenGenericArrays() Assert.True(typeof(IFace[]).IsAssignableFrom(a)); Assert.True(typeof(IEnumerable).IsAssignableFrom(a)); + Assert.True(a.IsAssignableTo(typeof(IFace[]))); + Assert.True(a.IsAssignableTo(typeof(IEnumerable))); + 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 IsEquivilentTo_TestData() diff --git a/src/libraries/System.Runtime/tests/System/Reflection/TypeDelegatorTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/TypeDelegatorTests.cs index d358512609c7ec..ca6dccd6a8c9bb 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/TypeDelegatorTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/TypeDelegatorTests.cs @@ -19,6 +19,17 @@ public void IsAssignableFrom() Assert.False(td.IsAssignableFrom(typeof(string))); } + [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() { diff --git a/src/tests/JIT/Intrinsics/TypeIntrinsics.IsAssignableTo.cs b/src/tests/JIT/Intrinsics/TypeIntrinsics.IsAssignableTo.cs index 4f1b1b59e3759a..6a7ccc73e362bd 100644 --- a/src/tests/JIT/Intrinsics/TypeIntrinsics.IsAssignableTo.cs +++ b/src/tests/JIT/Intrinsics/TypeIntrinsics.IsAssignableTo.cs @@ -182,7 +182,7 @@ public static void TestIsAssignableTo() IsFalse(IsAssignableTo, Dictionary>()); IsFalse(IsAssignableTo, Dictionary>()); IsFalse(IsAssignableTo, KeyValuePair>()); - IsFalse(IsAssignableTo, IEnumerable>, IEnumerable, IEnumerable>>()); + IsFalse(IsAssignableTo, IEnumerable>, KeyValuePair, IEnumerable>>()); } [MethodImpl(MethodImplOptions.NoInlining)] From b11b707f11e9ac52ac2f30128336ba258eefbb42 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 10 Aug 2020 18:18:30 +0100 Subject: [PATCH 8/9] Fix test method name --- src/libraries/System.Reflection/tests/TypeDerivedTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Reflection/tests/TypeDerivedTests.cs b/src/libraries/System.Reflection/tests/TypeDerivedTests.cs index 9ffda7c168742d..e9e341fbd5b683 100644 --- a/src/libraries/System.Reflection/tests/TypeDerivedTests.cs +++ b/src/libraries/System.Reflection/tests/TypeDerivedTests.cs @@ -27,7 +27,7 @@ public void IsAssignableFrom_NullUnderlyingSystemType() } [Fact] - public void IsAssignableFrom_NullUnderlyingSystemType() + public void IsAssignableTo_NullUnderlyingSystemType() { var testType = new TypeWithNullUnderlyingSystemType(); Assert.Null(testType.UnderlyingSystemType); From 20df2b9fb71ba001dbf6437dc2f0445977337dea Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 10 Aug 2020 20:36:26 +0100 Subject: [PATCH 9/9] Add null test --- src/libraries/System.Reflection/tests/TypeDerivedTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libraries/System.Reflection/tests/TypeDerivedTests.cs b/src/libraries/System.Reflection/tests/TypeDerivedTests.cs index e9e341fbd5b683..310caabe4b5dd9 100644 --- a/src/libraries/System.Reflection/tests/TypeDerivedTests.cs +++ b/src/libraries/System.Reflection/tests/TypeDerivedTests.cs @@ -36,6 +36,9 @@ public void IsAssignableTo_NullUnderlyingSystemType() 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)); } } }