Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type.IsAssignableTo #40326

Merged
merged 9 commits into from
Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ internal static object CreateDefaultEqualityComparer(Type type)
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);
}
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/src/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -3730,6 +3730,7 @@ class Compiler

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,
Expand Down
89 changes: 56 additions & 33 deletions src/coreclr/src/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/src/jit/namedintrinsiclist.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Type.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ 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;
benaadams marked this conversation as resolved.
Show resolved Hide resolved
protected virtual bool IsValueTypeImpl() => IsSubclassOf(typeof(ValueType));

public virtual bool IsSignatureType => false;
Expand Down
15 changes: 15 additions & 0 deletions src/libraries/System.Reflection/tests/TypeDerivedTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,20 @@ public void IsAssignableFrom_NullUnderlyingSystemType()
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));
benaadams marked this conversation as resolved.
Show resolved Hide resolved

Assert.False(testType.IsAssignableTo(null));
Assert.False(typeof(object).IsAssignableTo(null));
}
}
}
22 changes: 20 additions & 2 deletions src/libraries/System.Reflection/tests/TypeInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<object[]> IsInstanceOfType_TestData()
Expand Down Expand Up @@ -568,10 +569,13 @@ public void IsInstanceOfType(Type type, object o, bool expected)
[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
Expand All @@ -581,7 +585,7 @@ class G<T, U> where T : U
static volatile object s_boxedInt32;

[Fact]
public void IsAssignableFromNullable()
public void IsAssignableNullable()
{
Type nubInt = typeof(Nullable<int>);
Type intType = typeof(int);
Expand All @@ -592,6 +596,8 @@ public void IsAssignableFromNullable()
// 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];
Expand All @@ -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<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?)));

Expand Down Expand Up @@ -648,12 +661,17 @@ public void OpenGenericArrays()
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()
Expand Down
1 change: 1 addition & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
4 changes: 4 additions & 0 deletions src/mono/mono/metadata/reflection.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
vargaz marked this conversation as resolved.
Show resolved Hide resolved
g_assert (method);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Loading