Skip to content

Commit 7128331

Browse files
authored
Type.IsAssignableTo (#40326)
* IsAssignableTo * ifdef mono C change * Add Jit optimization * Add [Intrinsic] attribute * Add tests * More tests * Add null test
1 parent 8a734ff commit 7128331

File tree

15 files changed

+310
-41
lines changed

15 files changed

+310
-41
lines changed

src/coreclr/src/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ internal static object CreateDefaultEqualityComparer(Type type)
126126
result = new ByteEqualityComparer();
127127
}
128128
// If T implements IEquatable<T> return a GenericEqualityComparer<T>
129-
else if (typeof(IEquatable<>).MakeGenericType(type).IsAssignableFrom(type))
129+
else if (type.IsAssignableTo(typeof(IEquatable<>).MakeGenericType(type)))
130130
{
131131
result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericEqualityComparer<int>), runtimeType);
132132
}

src/coreclr/src/jit/compiler.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3730,6 +3730,7 @@ class Compiler
37303730

37313731
void impImportLeave(BasicBlock* block);
37323732
void impResetLeaveBlock(BasicBlock* block, unsigned jmpAddr);
3733+
GenTree* impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom);
37333734
GenTree* impIntrinsic(GenTree* newobjThis,
37343735
CORINFO_CLASS_HANDLE clsHnd,
37353736
CORINFO_METHOD_HANDLE method,

src/coreclr/src/jit/importer.cpp

Lines changed: 56 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4126,44 +4126,19 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
41264126

41274127
case NI_System_Type_IsAssignableFrom:
41284128
{
4129-
// Optimize patterns like:
4130-
//
4131-
// typeof(TTo).IsAssignableFrom(typeof(TTFrom))
4132-
// valueTypeVar.GetType().IsAssignableFrom(typeof(TTFrom))
4133-
//
4134-
// to true/false
41354129
GenTree* typeTo = impStackTop(1).val;
41364130
GenTree* typeFrom = impStackTop(0).val;
41374131

4138-
if (typeTo->IsCall() && typeFrom->IsCall())
4139-
{
4140-
// make sure both arguments are `typeof()`
4141-
CORINFO_METHOD_HANDLE hTypeof = eeFindHelper(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE);
4142-
if ((typeTo->AsCall()->gtCallMethHnd == hTypeof) && (typeFrom->AsCall()->gtCallMethHnd == hTypeof))
4143-
{
4144-
CORINFO_CLASS_HANDLE hClassTo =
4145-
gtGetHelperArgClassHandle(typeTo->AsCall()->gtCallArgs->GetNode());
4146-
CORINFO_CLASS_HANDLE hClassFrom =
4147-
gtGetHelperArgClassHandle(typeFrom->AsCall()->gtCallArgs->GetNode());
4148-
4149-
if (hClassTo == NO_CLASS_HANDLE || hClassFrom == NO_CLASS_HANDLE)
4150-
{
4151-
break;
4152-
}
4132+
retNode = impTypeIsAssignable(typeTo, typeFrom);
4133+
break;
4134+
}
41534135

4154-
TypeCompareState castResult = info.compCompHnd->compareTypesForCast(hClassFrom, hClassTo);
4155-
if (castResult == TypeCompareState::May)
4156-
{
4157-
// requires runtime check
4158-
// e.g. __Canon, COMObjects, Nullable
4159-
break;
4160-
}
4136+
case NI_System_Type_IsAssignableTo:
4137+
{
4138+
GenTree* typeTo = impStackTop(0).val;
4139+
GenTree* typeFrom = impStackTop(1).val;
41614140

4162-
retNode = gtNewIconNode((castResult == TypeCompareState::Must) ? 1 : 0);
4163-
impPopStack(); // drop both CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE calls
4164-
impPopStack();
4165-
}
4166-
}
4141+
retNode = impTypeIsAssignable(typeTo, typeFrom);
41674142
break;
41684143
}
41694144

@@ -4368,6 +4343,50 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
43684343
return retNode;
43694344
}
43704345

4346+
GenTree* Compiler::impTypeIsAssignable(GenTree* typeTo, GenTree* typeFrom)
4347+
{
4348+
// Optimize patterns like:
4349+
//
4350+
// typeof(TTo).IsAssignableFrom(typeof(TTFrom))
4351+
// valueTypeVar.GetType().IsAssignableFrom(typeof(TTFrom))
4352+
// typeof(TTFrom).IsAssignableTo(typeof(TTo))
4353+
// typeof(TTFrom).IsAssignableTo(valueTypeVar.GetType())
4354+
//
4355+
// to true/false
4356+
4357+
if (typeTo->IsCall() && typeFrom->IsCall())
4358+
{
4359+
// make sure both arguments are `typeof()`
4360+
CORINFO_METHOD_HANDLE hTypeof = eeFindHelper(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE);
4361+
if ((typeTo->AsCall()->gtCallMethHnd == hTypeof) && (typeFrom->AsCall()->gtCallMethHnd == hTypeof))
4362+
{
4363+
CORINFO_CLASS_HANDLE hClassTo = gtGetHelperArgClassHandle(typeTo->AsCall()->gtCallArgs->GetNode());
4364+
CORINFO_CLASS_HANDLE hClassFrom = gtGetHelperArgClassHandle(typeFrom->AsCall()->gtCallArgs->GetNode());
4365+
4366+
if (hClassTo == NO_CLASS_HANDLE || hClassFrom == NO_CLASS_HANDLE)
4367+
{
4368+
return nullptr;
4369+
}
4370+
4371+
TypeCompareState castResult = info.compCompHnd->compareTypesForCast(hClassFrom, hClassTo);
4372+
if (castResult == TypeCompareState::May)
4373+
{
4374+
// requires runtime check
4375+
// e.g. __Canon, COMObjects, Nullable
4376+
return nullptr;
4377+
}
4378+
4379+
GenTreeIntCon* retNode = gtNewIconNode((castResult == TypeCompareState::Must) ? 1 : 0);
4380+
impPopStack(); // drop both CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE calls
4381+
impPopStack();
4382+
4383+
return retNode;
4384+
}
4385+
}
4386+
4387+
return nullptr;
4388+
}
4389+
43714390
GenTree* Compiler::impMathIntrinsic(CORINFO_METHOD_HANDLE method,
43724391
CORINFO_SIG_INFO* sig,
43734392
var_types callType,
@@ -4614,6 +4633,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
46144633
{
46154634
result = NI_System_Type_IsAssignableFrom;
46164635
}
4636+
else if (strcmp(methodName, "IsAssignableTo") == 0)
4637+
{
4638+
result = NI_System_Type_IsAssignableTo;
4639+
}
46174640
}
46184641
}
46194642
#if defined(TARGET_XARCH) || defined(TARGET_ARM64)

src/coreclr/src/jit/namedintrinsiclist.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ enum NamedIntrinsic : unsigned short
3939
NI_System_GC_KeepAlive,
4040
NI_System_Type_get_IsValueType,
4141
NI_System_Type_IsAssignableFrom,
42+
NI_System_Type_IsAssignableTo,
4243

4344
// These are used by HWIntrinsics but are defined more generally
4445
// to allow dead code optimization and handle the recursion case

src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,8 @@ internal void ToString(TraceFormat traceFormat, StringBuilder sb)
227227
bool methodChanged = false;
228228
if (declaringType != null && declaringType.IsDefined(typeof(CompilerGeneratedAttribute), inherit: false))
229229
{
230-
isAsync = typeof(IAsyncStateMachine).IsAssignableFrom(declaringType);
231-
if (isAsync || typeof(IEnumerator).IsAssignableFrom(declaringType))
230+
isAsync = declaringType.IsAssignableTo(typeof(IAsyncStateMachine));
231+
if (isAsync || declaringType.IsAssignableTo(typeof(IEnumerator)))
232232
{
233233
methodChanged = TryResolveStateMachineMethod(ref mb, out declaringType);
234234
}

src/libraries/System.Private.CoreLib/src/System/Type.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ public virtual Type[] GetGenericParameterConstraints()
109109
public bool IsPrimitive => IsPrimitiveImpl();
110110
protected abstract bool IsPrimitiveImpl();
111111
public bool IsValueType { [Intrinsic] get => IsValueTypeImpl(); }
112+
113+
[Intrinsic]
114+
public bool IsAssignableTo([NotNullWhen(true)] Type? targetType) => targetType?.IsAssignableFrom(this) ?? false;
112115
protected virtual bool IsValueTypeImpl() => IsSubclassOf(typeof(ValueType));
113116

114117
public virtual bool IsSignatureType => false;

src/libraries/System.Reflection/tests/TypeDerivedTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,20 @@ public void IsAssignableFrom_NullUnderlyingSystemType()
2525
Assert.False(testType.IsAssignableFrom(compareType));
2626
Assert.False(compareType.IsAssignableFrom(testType));
2727
}
28+
29+
[Fact]
30+
public void IsAssignableTo_NullUnderlyingSystemType()
31+
{
32+
var testType = new TypeWithNullUnderlyingSystemType();
33+
Assert.Null(testType.UnderlyingSystemType);
34+
Assert.True(testType.IsAssignableTo(testType));
35+
36+
Type compareType = typeof(int);
37+
Assert.False(testType.IsAssignableTo(compareType));
38+
Assert.False(compareType.IsAssignableTo(testType));
39+
40+
Assert.False(testType.IsAssignableTo(null));
41+
Assert.False(typeof(object).IsAssignableTo(null));
42+
}
2843
}
2944
}

src/libraries/System.Reflection/tests/TypeInfoTests.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ public void ImplementedInterfaces(Type type, Type[] expected)
498498

499499
Assert.Equal(expected, implementedInterfaces);
500500
Assert.All(expected, ti => Assert.True(ti.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())));
501+
Assert.All(expected, ti => Assert.True(type.GetTypeInfo().IsAssignableTo(ti.GetTypeInfo())));
501502
}
502503

503504
public static IEnumerable<object[]> IsInstanceOfType_TestData()
@@ -568,10 +569,13 @@ public void IsInstanceOfType(Type type, object o, bool expected)
568569
[InlineData(typeof(uint[]), typeof(int[]), true)]
569570
[InlineData(typeof(IList<int>), typeof(int[]), true)]
570571
[InlineData(typeof(IList<uint>), typeof(int[]), true)]
571-
public void IsAssignableFrom(Type type, Type c, bool expected)
572+
public void IsAssignable(Type type, Type c, bool expected)
572573
{
573574
Assert.Equal(expected, type.GetTypeInfo().IsAssignableFrom(c));
574575
Assert.Equal(expected, type.GetTypeInfo().IsAssignableFrom(c?.GetTypeInfo()));
576+
577+
Assert.Equal(expected, c?.IsAssignableTo(type) ?? false);
578+
Assert.Equal(expected, c?.GetTypeInfo().IsAssignableTo(type.GetTypeInfo()) ?? false);
575579
}
576580

577581
class G<T, U> where T : U
@@ -581,7 +585,7 @@ class G<T, U> where T : U
581585
static volatile object s_boxedInt32;
582586

583587
[Fact]
584-
public void IsAssignableFromNullable()
588+
public void IsAssignableNullable()
585589
{
586590
Type nubInt = typeof(Nullable<int>);
587591
Type intType = typeof(int);
@@ -592,6 +596,8 @@ public void IsAssignableFromNullable()
592596
// Nullable<T> is assignable from int
593597
Assert.True(nubInt.IsAssignableFrom(intType));
594598
Assert.False(intType.IsAssignableFrom(nubInt));
599+
Assert.False(nubInt.IsAssignableTo(intType));
600+
Assert.True(intType.IsAssignableTo(nubInt));
595601

596602
Type nubOfT = nubInt.GetGenericTypeDefinition();
597603
Type T = nubOfT.GetTypeInfo().GenericTypeParameters[0];
@@ -601,11 +607,18 @@ public void IsAssignableFromNullable()
601607
Assert.True(objType.IsAssignableFrom(T));
602608
Assert.True(valTypeType.IsAssignableFrom(T));
603609

610+
Assert.True(T.IsAssignableTo(T));
611+
Assert.True(T.IsAssignableTo(objType));
612+
Assert.True(T.IsAssignableTo(valTypeType));
613+
604614
// should be false
605615
// Nullable<T> is not assignable from T
606616
Assert.False(nubOfT.IsAssignableFrom(T));
607617
Assert.False(T.IsAssignableFrom(nubOfT));
608618

619+
Assert.False(nubOfT.IsAssignableTo(T));
620+
Assert.False(T.IsAssignableTo(nubOfT));
621+
609622
// illegal type construction due to T->T?
610623
Assert.Throws<ArgumentException>(() => typeof(G<,>).MakeGenericType(typeof(int), typeof(int?)));
611624

@@ -648,12 +661,17 @@ public void OpenGenericArrays()
648661
Assert.True(typeof(IFace[]).IsAssignableFrom(a));
649662
Assert.True(typeof(IEnumerable<IFace>).IsAssignableFrom(a));
650663

664+
Assert.True(a.IsAssignableTo(typeof(IFace[])));
665+
Assert.True(a.IsAssignableTo(typeof(IEnumerable<IFace>)));
666+
651667
Type a1 = typeof(GG<,>).GetGenericArguments()[0].MakeArrayType();
652668
Type a2 = typeof(GG<,>).GetGenericArguments()[1].MakeArrayType();
653669
Assert.True(a2.IsAssignableFrom(a1));
670+
Assert.True(a1.IsAssignableTo(a2));
654671

655672
Type ie = typeof(IEnumerable<>).MakeGenericType(typeof(GG<,>).GetGenericArguments()[1]);
656673
Assert.True(ie.IsAssignableFrom(a1));
674+
Assert.True(a1.IsAssignableTo(ie));
657675
}
658676

659677
public static IEnumerable<object[]> IsEquivilentTo_TestData()

src/libraries/System.Runtime/ref/System.Runtime.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4337,6 +4337,7 @@ protected Type() { }
43374337
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);
43384338
protected abstract bool IsArrayImpl();
43394339
public virtual bool IsAssignableFrom([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] System.Type? c) { throw null; }
4340+
public bool IsAssignableTo([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] System.Type? targetType) { throw null; }
43404341
protected abstract bool IsByRefImpl();
43414342
protected abstract bool IsCOMObjectImpl();
43424343
protected virtual bool IsContextfulImpl() { throw null; }

src/libraries/System.Runtime/tests/System/Reflection/TypeDelegatorTests.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ public void IsAssignableFrom()
1919
Assert.False(td.IsAssignableFrom(typeof(string)));
2020
}
2121

22+
[Fact]
23+
public void IsAssignableTo()
24+
{
25+
TypeDelegator td = new TypeDelegator(typeof(int));
26+
27+
Assert.True(td.IsAssignableTo(typeof(int)));
28+
Assert.False(td.IsAssignableTo(typeof(string)));
29+
Assert.True(typeof(int).IsAssignableTo(td));
30+
Assert.False(typeof(string).IsAssignableTo(td));
31+
}
32+
2233
[Fact]
2334
public void CreateInstance()
2435
{

src/mono/mono/metadata/reflection.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3131,7 +3131,11 @@ mono_reflection_call_is_assignable_to (MonoClass *klass, MonoClass *oklass, Mono
31313131
error_init (error);
31323132

31333133
if (method == NULL) {
3134+
#ifdef ENABLE_NETCORE
3135+
method = mono_class_get_method_from_name_checked (mono_class_get_type_builder_class (), "IsAssignableToInternal", 1, 0, error);
3136+
#else
31343137
method = mono_class_get_method_from_name_checked (mono_class_get_type_builder_class (), "IsAssignableTo", 1, 0, error);
3138+
#endif
31353139
mono_error_assert_ok (error);
31363140
g_assert (method);
31373141
}

src/mono/netcore/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.Mono.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ protected override TypeAttributes GetAttributeFlagsImpl()
9090
}
9191

9292
[DynamicDependency(nameof(state))] // Automatically keeps all previous fields too due to StructLayout
93-
[DynamicDependency(nameof(IsAssignableTo))] // Used from reflection.c: mono_reflection_call_is_assignable_to
93+
[DynamicDependency(nameof(IsAssignableToInternal))] // Used from reflection.c: mono_reflection_call_is_assignable_to
9494
internal TypeBuilder(ModuleBuilder mb, TypeAttributes attr, int table_idx)
9595
{
9696
this.parent = null;
@@ -107,7 +107,7 @@ internal TypeBuilder(ModuleBuilder mb, TypeAttributes attr, int table_idx)
107107
Justification = "Linker doesn't analyze ResolveUserType but it's an identity function")]
108108

109109
[DynamicDependency(nameof(state))] // Automatically keeps all previous fields too due to StructLayout
110-
[DynamicDependency(nameof(IsAssignableTo))] // Used from reflection.c: mono_reflection_call_is_assignable_to
110+
[DynamicDependency(nameof(IsAssignableToInternal))] // Used from reflection.c: mono_reflection_call_is_assignable_to
111111
internal TypeBuilder(ModuleBuilder mb, string fullname, TypeAttributes attr, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]Type? parent, Type[]? interfaces, PackingSize packing_size, int type_size, Type? nesting_type)
112112
{
113113
int sep_index;
@@ -1718,7 +1718,7 @@ public override bool IsAssignableFrom([NotNullWhen(true)] Type? c)
17181718
}
17191719

17201720
// FIXME: "arrays"
1721-
internal bool IsAssignableTo([NotNullWhen(true)] Type? c)
1721+
internal bool IsAssignableToInternal([NotNullWhen(true)] Type? c)
17221722
{
17231723
if (c == this)
17241724
return true;

0 commit comments

Comments
 (0)