mirror of
https://github.com/pardeike/Harmony
synced 2026-06-08 16:40:38 +00:00
AccessTools.*FieldRefAccess improvements, including struct support
Specifically, handle edge cases and have consistent error handling in AccessTools.*FieldRefAccess. Add AccessTools.StructFieldRef/StructFieldRefAccess.
This commit is contained in:
+447
-72
@@ -782,30 +782,69 @@ namespace HarmonyLib
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>A read/writable reference to an instance field</summary>
|
||||
/// <typeparam name="T">The class the field is defined in or "object" if type cannot be accessed at compile time</typeparam>
|
||||
/// <typeparam name="F">The type of the field</typeparam>
|
||||
/// <param name="obj">The runtime instance to access the field (leave empty for static fields)</param>
|
||||
/// <returns>An readable/assignable object representing the field</returns>
|
||||
/// <summary>A readable/assignable reference delegate to an instance field of a class or static field (NOT an instance field of a struct)</summary>
|
||||
/// <typeparam name="T">
|
||||
/// An arbitrary type if the field is static; otherwise the class that defines the field, or a parent class (including <see cref="object"/>),
|
||||
/// implemented interface, or derived class of this type
|
||||
/// </typeparam>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="instance">The runtime instance to access the field (ignored and can be omitted for static fields)</param>
|
||||
/// <returns>A readable/assignable reference to the field</returns>
|
||||
/// <exception cref="NullReferenceException">Null instance passed to a non-static field ref delegate</exception>
|
||||
/// <exception cref="InvalidCastException">
|
||||
/// Instance of invalid type passed to a non-static field ref delegate
|
||||
/// (this can happen if <typeparamref name="T"/> is a parent class or interface of the field's declaring type)
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This delegate cannot be used for instance fields of structs, since a struct instance passed to the delegate would be passed by
|
||||
/// value and thus would be a copy that only exists within the delegate's invocation. This is fine for a readonly reference,
|
||||
/// but makes assigning to the reference an exercise in futility. Use <see cref="StructFieldRef{T, F}"/> instead.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Note that <typeparamref name="T"/> is not required to be the field's declaring type. It can be a parent class (including <see cref="object"/>),
|
||||
/// implemented interface, or a derived class of the field's declaring type ("<c>instanceOfT is FieldDeclaringType</c>" must be possible).
|
||||
/// Specifically, <typeparamref name="F"/> must be <see cref="Type.IsAssignableFrom(Type)">assignable from</see> OR to the field's declaring type.
|
||||
/// Technically, this allows <c>Nullable</c>, although <c>Nullable</c> is only relevant for structs, and since only static fields of structs
|
||||
/// are allowed for this delegate, and the instance passed to such a delegate is ignored, this hardly matters.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Similarly, <typeparamref name="F"/> is not required to be the field's field type, unless that type is a value type.
|
||||
/// It can be a parent class (including <c>object</c>) or implemented interface of the field's field type. It cannot be a derived class.
|
||||
/// This variance is not allowed for value types, since that would require boxing/unboxing, which is not allowed for ref values.
|
||||
/// Specifically, for reference types, <typeparamref name="F"/> must be <see cref="Type.IsAssignableFrom(Type)">assignable from</see>
|
||||
/// the field's field type; and for value types, <typeparamref name="F"/> must be exactly the field's field type.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This delegate supports static fields, even those defined in structs, for legacy reasons.
|
||||
/// For such static fields, <typeparamref name="T"/> is effectively ignored.
|
||||
/// This is also the reason that this delegate lacks a generic class constraint (it was added to certain <c>FieldRefAccess</c> methods,
|
||||
/// but such a constraint cannot be added to this delegate without breaking binary compatibility).
|
||||
/// Consider using <see cref="FieldRef{F}"/> (and <c>StaticFieldRefAccess</c> methods that return it) instead for static fields.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
///
|
||||
public delegate ref F FieldRef<T, F>(T obj = default);
|
||||
public delegate ref F FieldRef<T, F>(T instance = default);
|
||||
|
||||
/// <summary>Creates an instance field reference</summary>
|
||||
/// <typeparam name="T">The class the field is defined in</typeparam>
|
||||
/// <typeparam name="F">The type of the field</typeparam>
|
||||
/// <summary>Creates a field reference delegate for an instance field of a class</summary>
|
||||
/// <typeparam name="T">The class that defines the instance field, or derived class of this type</typeparam>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="fieldName">The name of the field</param>
|
||||
/// <returns>A read and writable field reference delegate</returns>
|
||||
/// <returns>A readable/assignable <see cref="FieldRef{T,F}"/> delegate</returns>
|
||||
///
|
||||
public static FieldRef<T, F> FieldRefAccess<T, F>(string fieldName)
|
||||
public static FieldRef<T, F> FieldRefAccess<T, F>(string fieldName) where T : class
|
||||
{
|
||||
const BindingFlags bf = BindingFlags.NonPublic |
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.DeclaredOnly;
|
||||
|
||||
if (fieldName is null)
|
||||
throw new ArgumentNullException(nameof(fieldName));
|
||||
try
|
||||
{
|
||||
var fi = typeof(T).GetField(fieldName, bf);
|
||||
return FieldRefAccess<T, F>(fi);
|
||||
return FieldRefAccessInternal<T, F>(GetInstanceField(typeof(T), fieldName), needCastclass: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -813,62 +852,363 @@ namespace HarmonyLib
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates an instance field reference for a specific instance</summary>
|
||||
/// <typeparam name="T">The class the field is defined in</typeparam>
|
||||
/// <typeparam name="F">The type of the field</typeparam>
|
||||
/// <summary>Creates an instance field reference for a specific instance of a class</summary>
|
||||
/// <typeparam name="T">The class that defines the instance field, or derived class of this type</typeparam>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="instance">The instance</param>
|
||||
/// <param name="fieldName">The name of the field</param>
|
||||
/// <returns>An readable/assignable object representing the field</returns>
|
||||
/// <returns>A readable/assignable reference to the field</returns>
|
||||
///
|
||||
public static ref F FieldRefAccess<T, F>(T instance, string fieldName)
|
||||
public static ref F FieldRefAccess<T, F>(T instance, string fieldName) where T : class
|
||||
{
|
||||
return ref FieldRefAccess<T, F>(fieldName)(instance);
|
||||
if (instance is null)
|
||||
throw new ArgumentNullException(nameof(instance));
|
||||
if (fieldName is null)
|
||||
throw new ArgumentNullException(nameof(fieldName));
|
||||
try
|
||||
{
|
||||
return ref FieldRefAccessInternal<T, F>(GetInstanceField(typeof(T), fieldName), needCastclass: false)(instance);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException($"FieldRefAccess<{typeof(T)}, {typeof(F)}> for {instance}, {fieldName} caused an exception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates an instance field reference delegate for a private type</summary>
|
||||
/// <typeparam name="F">The type of the field</typeparam>
|
||||
/// <param name="type">The class/type</param>
|
||||
/// <summary>Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct)</summary>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="type">
|
||||
/// The type that defines the field, or derived class of this type; must not be a struct type unless the field is static
|
||||
/// </param>
|
||||
/// <param name="fieldName">The name of the field</param>
|
||||
/// <returns>A read and writable <see cref="FieldRef{T,F}"/> delegate</returns>
|
||||
/// <returns>
|
||||
/// A readable/assignable <see cref="FieldRef{T,F}"/> delegate with <c>T=object</c>
|
||||
/// (for static fields, the <c>instance</c> delegate parameter is ignored)
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method supports static fields, even those defined in structs, for legacy reasons.
|
||||
/// Consider using <see cref="StaticFieldRefAccess{F}(Type, string)"/> (and other overloads) instead for static fields.
|
||||
/// </remarks>
|
||||
///
|
||||
public static FieldRef<object, F> FieldRefAccess<F>(Type type, string fieldName)
|
||||
{
|
||||
return FieldRefAccess<object, F>(Field(type, fieldName));
|
||||
if (type is null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
if (fieldName is null)
|
||||
throw new ArgumentNullException(nameof(fieldName));
|
||||
try
|
||||
{
|
||||
var fieldInfo = Field(type, fieldName);
|
||||
if (fieldInfo is null)
|
||||
throw new MissingFieldException(type.Name, fieldName);
|
||||
// Backwards compatibility: This supports static fields, even those defined in structs. For static fields, T is effectively ignored.
|
||||
if (fieldInfo.IsStatic is false && fieldInfo.DeclaringType is Type declaringType)
|
||||
{
|
||||
// When fieldInfo is passed to FieldRefAccess methods, the T generic class constraint is insufficient to ensure that
|
||||
// the field is not a struct instance field, since T could be object, ValueType, or an interface that the struct implements.
|
||||
if (declaringType.IsValueType)
|
||||
throw new ArgumentException("Either FieldDeclaringType must be a class or field must be static");
|
||||
}
|
||||
// Field's declaring type cannot be object, since object has no fields, so always need a castclass for T=object.
|
||||
return FieldRefAccessInternal<object, F>(fieldInfo, needCastclass: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException($"FieldRefAccess<{typeof(F)}> for {type}, {fieldName} caused an exception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates an instance field reference delegate for a fieldinfo</summary>
|
||||
/// <typeparam name="T">The class the field is defined in or "object" if type cannot be accessed at compile time</typeparam>
|
||||
/// <typeparam name="F">The type of the field</typeparam>
|
||||
/// <param name="fieldInfo">The field of the field</param>
|
||||
/// <returns>A read and writable <see cref="FieldRef{T,F}"/> delegate</returns>
|
||||
/// <summary>Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct)</summary>
|
||||
/// <typeparam name="T">
|
||||
/// An arbitrary type if the field is static; otherwise the class that defines the field, or a parent class (including <see cref="object"/>),
|
||||
/// implemented interface, or derived class of this type ("<c>instanceOfT is FieldDeclaringType</c>" must be possible)
|
||||
/// </typeparam>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="fieldInfo">The field</param>
|
||||
/// <returns>A readable/assignable <see cref="FieldRef{T,F}"/> delegate</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method supports static fields, even those defined in structs, for legacy reasons.
|
||||
/// For such static fields, <typeparamref name="T"/> is effectively ignored.
|
||||
/// Consider using <see cref="StaticFieldRefAccess{T, F}(FieldInfo)"/> (and other overloads) instead for static fields.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
///
|
||||
public static FieldRef<T, F> FieldRefAccess<T, F>(FieldInfo fieldInfo)
|
||||
public static FieldRef<T, F> FieldRefAccess<T, F>(FieldInfo fieldInfo) where T : class
|
||||
{
|
||||
if (fieldInfo == null)
|
||||
if (fieldInfo is null)
|
||||
throw new ArgumentNullException(nameof(fieldInfo));
|
||||
if (!typeof(F).IsAssignableFrom(fieldInfo.FieldType))
|
||||
throw new ArgumentException("FieldInfo type does not match FieldRefAccess return type.");
|
||||
if (typeof(T) != typeof(object))
|
||||
if (fieldInfo.DeclaringType == null || !fieldInfo.DeclaringType.IsAssignableFrom(typeof(T)))
|
||||
throw new MissingFieldException(typeof(T).Name, fieldInfo.Name);
|
||||
try
|
||||
{
|
||||
var needCastclass = false;
|
||||
// Backwards compatibility: FieldRefAccess<F>(Type type, string fieldName) used to delegate to this method,
|
||||
// and thus this method must support the same cases - namely, static fields. For static fields, T is effectively ignored.
|
||||
if (fieldInfo.IsStatic is false && fieldInfo.DeclaringType is Type declaringType)
|
||||
{
|
||||
// When fieldInfo is passed to FieldRefAccess methods, the T generic class constraint is insufficient to ensure that
|
||||
// the field is not a struct instance field, since T could be object, ValueType, or an interface that the struct implements.
|
||||
if (declaringType.IsValueType)
|
||||
throw new ArgumentException("Either FieldDeclaringType must be a class or field must be static");
|
||||
needCastclass = FieldRefNeedsClasscast(typeof(T), declaringType);
|
||||
}
|
||||
return FieldRefAccessInternal<T, F>(fieldInfo, needCastclass);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException($"FieldRefAccess<{typeof(T)}, {typeof(F)}> for {fieldInfo} caused an exception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
var s_name = $"__refget_{typeof(T).Name}_fi_{fieldInfo.Name}";
|
||||
/// <summary>Creates a field reference for an instance field of a class</summary>
|
||||
/// <typeparam name="T">
|
||||
/// The type that defines the field; or a parent class (including <see cref="object"/>), implemented interface, or derived class of this type
|
||||
/// ("<c>instanceOfT is FieldDeclaringType</c>" must be possible)
|
||||
/// </typeparam>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="instance">The instance</param>
|
||||
/// <param name="fieldInfo">The field</param>
|
||||
/// <returns>A readable/assignable reference to the field</returns>
|
||||
///
|
||||
public static ref F FieldRefAccess<T, F>(T instance, FieldInfo fieldInfo) where T : class
|
||||
{
|
||||
if (instance is null)
|
||||
throw new ArgumentNullException(nameof(instance));
|
||||
if (fieldInfo is null)
|
||||
throw new ArgumentNullException(nameof(fieldInfo));
|
||||
try
|
||||
{
|
||||
if (fieldInfo.IsStatic)
|
||||
throw new ArgumentException("Field must not be static");
|
||||
var needCastclass = false;
|
||||
if (fieldInfo.DeclaringType is Type declaringType)
|
||||
{
|
||||
// When fieldInfo is passed to FieldRefAccess methods, the T generic class constraint is insufficient to ensure that
|
||||
// the field is not a struct instance field, since T could be object, ValueType, or an interface that the struct implements.
|
||||
if (declaringType.IsValueType)
|
||||
throw new ArgumentException("FieldDeclaringType must be a class");
|
||||
needCastclass = FieldRefNeedsClasscast(typeof(T), declaringType);
|
||||
}
|
||||
return ref FieldRefAccessInternal<T, F>(fieldInfo, needCastclass)(instance);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException($"FieldRefAccess<{typeof(T)}, {typeof(F)}> for {instance}, {fieldInfo} caused an exception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
var dm = new DynamicMethodDefinition(s_name, typeof(F).MakeByRefType(), new[] { typeof(T) });
|
||||
static bool FieldRefNeedsClasscast(Type delegateInstanceType, Type declaringType)
|
||||
{
|
||||
var needCastclass = false;
|
||||
if (delegateInstanceType != declaringType)
|
||||
{
|
||||
needCastclass = delegateInstanceType.IsAssignableFrom(declaringType);
|
||||
if (needCastclass is false && declaringType.IsAssignableFrom(delegateInstanceType) is false)
|
||||
throw new ArgumentException("FieldDeclaringType must be assignable from or to T (FieldRefAccess instance type) - " +
|
||||
"\"instanceOfT is FieldDeclaringType\" must be possible");
|
||||
}
|
||||
return needCastclass;
|
||||
}
|
||||
|
||||
static FieldRef<T, F> FieldRefAccessInternal<T, F>(FieldInfo fieldInfo, bool needCastclass) where T : class
|
||||
{
|
||||
ValidateFieldType<F>(fieldInfo);
|
||||
var delegateInstanceType = typeof(T);
|
||||
var declaringType = fieldInfo.DeclaringType;
|
||||
|
||||
var dm = new DynamicMethodDefinition($"__refget_{delegateInstanceType.Name}_fi_{fieldInfo.Name}",
|
||||
typeof(F).MakeByRefType(), new[] { delegateInstanceType });
|
||||
|
||||
var il = dm.GetILGenerator();
|
||||
// Backwards compatibility: This supports static fields, even those defined in structs.
|
||||
if (fieldInfo.IsStatic)
|
||||
{
|
||||
// ldarg.0 + ldflda actually works for static fields, but the potential castclass (and InvalidCastException) below must be avoided
|
||||
// so might as well use the singular ldsflda for static fields.
|
||||
il.Emit(OpCodes.Ldsflda, fieldInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
// The castclass is needed when T is a parent class or interface of declaring type (e.g. if T is object),
|
||||
// since there's no guarantee the instance passed to the delegate is actually of the declaring type.
|
||||
// In such a situation, the castclass will throw an InvalidCastException and thus prevent undefined behavior.
|
||||
if (needCastclass)
|
||||
il.Emit(OpCodes.Castclass, declaringType);
|
||||
il.Emit(OpCodes.Ldflda, fieldInfo);
|
||||
}
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return (FieldRef<T, F>)dm.Generate().CreateDelegate(typeof(FieldRef<T, F>));
|
||||
}
|
||||
|
||||
/// <summary>A readable/assignable reference delegate to an instance field of a struct</summary>
|
||||
/// <typeparam name="T">The struct that defines the instance field</typeparam>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="instance">A reference to the runtime instance to access the field</param>
|
||||
/// <returns>A readable/assignable reference to the field</returns>
|
||||
///
|
||||
public delegate ref F StructFieldRef<T, F>(ref T instance) where T : struct;
|
||||
|
||||
/// <summary>Creates a field reference delegate for an instance field of a struct</summary>
|
||||
/// <typeparam name="T">The struct that defines the instance field</typeparam>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="fieldName">The name of the field</param>
|
||||
/// <returns>A readable/assignable <see cref="StructFieldRef{T,F}"/> delegate</returns>
|
||||
///
|
||||
public static StructFieldRef<T, F> StructFieldRefAccess<T, F>(string fieldName) where T : struct
|
||||
{
|
||||
if (fieldName is null)
|
||||
throw new ArgumentNullException(nameof(fieldName));
|
||||
try
|
||||
{
|
||||
return StructFieldRefAccessInternal<T, F>(GetInstanceField(typeof(T), fieldName));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException($"StructFieldRefAccess<{typeof(T)}, {typeof(F)}> for {fieldName} caused an exception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates an instance field reference for a specific instance of a struct</summary>
|
||||
/// <typeparam name="T">The struct that defines the instance field</typeparam>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="instance">The instance</param>
|
||||
/// <param name="fieldName">The name of the field</param>
|
||||
/// <returns>A readable/assignable reference to the field</returns>
|
||||
///
|
||||
public static ref F StructFieldRefAccess<T, F>(ref T instance, string fieldName) where T : struct
|
||||
{
|
||||
if (fieldName is null)
|
||||
throw new ArgumentNullException(nameof(fieldName));
|
||||
try
|
||||
{
|
||||
return ref StructFieldRefAccessInternal<T, F>(GetInstanceField(typeof(T), fieldName))(ref instance);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException($"StructFieldRefAccess<{typeof(T)}, {typeof(F)}> for {instance}, {fieldName} caused an exception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a field reference delegate for an instance field of a struct</summary>
|
||||
/// <typeparam name="T">The struct that defines the instance field</typeparam>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="fieldInfo">The field</param>
|
||||
/// <returns>A readable/assignable <see cref="StructFieldRef{T,F}"/> delegate</returns>
|
||||
public static StructFieldRef<T, F> StructFieldRefAccess<T, F>(FieldInfo fieldInfo) where T : struct
|
||||
{
|
||||
if (fieldInfo is null)
|
||||
throw new ArgumentNullException(nameof(fieldInfo));
|
||||
try
|
||||
{
|
||||
ValidateStructField<T, F>(fieldInfo);
|
||||
return StructFieldRefAccessInternal<T, F>(fieldInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException($"StructFieldRefAccess<{typeof(T)}, {typeof(F)}> for {fieldInfo} caused an exception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a field reference for an instance field of a struct</summary>
|
||||
/// <typeparam name="T">The struct that defines the instance field</typeparam>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="instance">The instance</param>
|
||||
/// <param name="fieldInfo">The field</param>
|
||||
/// <returns>A readable/assignable reference to the field</returns>
|
||||
///
|
||||
public static ref F StructFieldRefAccess<T, F>(ref T instance, FieldInfo fieldInfo) where T : struct
|
||||
{
|
||||
if (fieldInfo is null)
|
||||
throw new ArgumentNullException(nameof(fieldInfo));
|
||||
try
|
||||
{
|
||||
ValidateStructField<T, F>(fieldInfo);
|
||||
return ref StructFieldRefAccessInternal<T, F>(fieldInfo)(ref instance);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException($"StructFieldRefAccess<{typeof(T)}, {typeof(F)}> for {instance}, {fieldInfo} caused an exception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
static void ValidateStructField<T, F>(FieldInfo fieldInfo) where T : struct
|
||||
{
|
||||
if (fieldInfo.IsStatic)
|
||||
throw new ArgumentException("Field must not be static");
|
||||
if (fieldInfo.DeclaringType != typeof(T))
|
||||
throw new ArgumentException("FieldDeclaringType must be T (StructFieldRefAccess instance type)");
|
||||
}
|
||||
|
||||
static StructFieldRef<T, F> StructFieldRefAccessInternal<T, F>(FieldInfo fieldInfo) where T : struct
|
||||
{
|
||||
ValidateFieldType<F>(fieldInfo);
|
||||
|
||||
var dm = new DynamicMethodDefinition($"__refget_{typeof(T).Name}_struct_fi_{fieldInfo.Name}",
|
||||
typeof(F).MakeByRefType(), new[] { typeof(T).MakeByRefType() });
|
||||
|
||||
var il = dm.GetILGenerator();
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldflda, fieldInfo);
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return (FieldRef<T, F>)dm.Generate().CreateDelegate(typeof(FieldRef<T, F>));
|
||||
return (StructFieldRef<T, F>)dm.Generate().CreateDelegate(typeof(StructFieldRef<T, F>));
|
||||
}
|
||||
|
||||
static FieldInfo GetInstanceField(Type type, string fieldName)
|
||||
{
|
||||
var fieldInfo = Field(type, fieldName);
|
||||
if (fieldInfo is null)
|
||||
throw new MissingFieldException(type.Name, fieldName);
|
||||
if (fieldInfo.IsStatic)
|
||||
throw new ArgumentException("Field must not be static");
|
||||
return fieldInfo;
|
||||
}
|
||||
|
||||
/// <summary>A readable/assignable reference delegate to a static field</summary>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <returns>A readable/assignable reference to the field</returns>
|
||||
///
|
||||
public delegate ref F FieldRef<F>();
|
||||
|
||||
/// <summary>Creates a static field reference</summary>
|
||||
/// <typeparam name="T">The class the field is defined in or "object" if type cannot be accessed at compile time</typeparam>
|
||||
/// <typeparam name="F">The type of the field</typeparam>
|
||||
/// <typeparam name="T">The type (can be class or struct) the field is defined in</typeparam>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="fieldName">The name of the field</param>
|
||||
/// <returns>An readable/assignable object representing the static field</returns>
|
||||
/// <returns>A readable/assignable reference to the field</returns>
|
||||
///
|
||||
public static ref F StaticFieldRefAccess<T, F>(string fieldName)
|
||||
{
|
||||
@@ -876,67 +1216,86 @@ namespace HarmonyLib
|
||||
}
|
||||
|
||||
/// <summary>Creates a static field reference</summary>
|
||||
/// <typeparam name="F">The type of the field</typeparam>
|
||||
/// <param name="type">The class/type</param>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="type">The type (can be class or struct) the field is defined in</param>
|
||||
/// <param name="fieldName">The name of the field</param>
|
||||
/// <returns>An readable/assignable object representing the static field</returns>
|
||||
/// <returns>A readable/assignable reference to the field</returns>
|
||||
///
|
||||
public static ref F StaticFieldRefAccess<F>(Type type, string fieldName)
|
||||
{
|
||||
const BindingFlags bf = BindingFlags.NonPublic |
|
||||
BindingFlags.Static |
|
||||
BindingFlags.DeclaredOnly;
|
||||
try
|
||||
{
|
||||
var fi = type.GetField(fieldName, bf);
|
||||
return ref StaticFieldRefAccess<F>(fi)();
|
||||
var fieldInfo = Field(type, fieldName);
|
||||
if (fieldInfo is null)
|
||||
throw new MissingFieldException(type.Name, fieldName);
|
||||
return ref StaticFieldRefAccessInternal<F>(fieldInfo)();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException($"StaticFieldRefAccess<{typeof(F)}> for {type}, {fieldName} caused an exception", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a static field reference</summary>
|
||||
/// <typeparam name="T">The class the field is defined in or "object" if type cannot be accessed at compile time</typeparam>
|
||||
/// <typeparam name="F">The type of the field</typeparam>
|
||||
/// <typeparam name="T">An arbitrary type (by convention, the type the field is defined in)</typeparam>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="fieldInfo">The field</param>
|
||||
/// <returns>An readable/assignable object representing the static field</returns>
|
||||
/// <returns>A readable/assignable reference to the field</returns>
|
||||
/// <remarks>
|
||||
/// The type parameter <typeparamref name="T"/> is only used in exception messaging and to distinguish between this method overload
|
||||
/// and the <see cref="StaticFieldRefAccess{F}(FieldInfo)"/> overload (which returns a <see cref="FieldRef{F}"/> rather than a reference).
|
||||
/// </remarks>
|
||||
///
|
||||
public static ref F StaticFieldRefAccess<T, F>(FieldInfo fieldInfo)
|
||||
{
|
||||
if (fieldInfo is null)
|
||||
throw new ArgumentNullException(nameof(fieldInfo));
|
||||
try
|
||||
{
|
||||
return ref StaticFieldRefAccess<F>(fieldInfo)();
|
||||
return ref StaticFieldRefAccessInternal<F>(fieldInfo)();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException($"StaticFieldRefAccess<{typeof(T)}, {typeof(F)}> for {fieldInfo} caused an exception", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>A read/writable reference delegate to a static field</summary>
|
||||
/// <typeparam name="F">The type of the field</typeparam>
|
||||
/// <returns>An readable/assignable object representing the static field</returns>
|
||||
///
|
||||
public delegate ref F FieldRef<F>();
|
||||
|
||||
/// <summary>Creates a static field reference delegate</summary>
|
||||
/// <typeparam name="F">The type of the field</typeparam>
|
||||
/// <typeparam name="F">
|
||||
/// The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type),
|
||||
/// a type that <see cref="Type.IsAssignableFrom(Type)">is assignable from</see> the field's type
|
||||
/// </typeparam>
|
||||
/// <param name="fieldInfo">The field</param>
|
||||
/// <returns>A read and writable <see cref="FieldRef{F}"/> delegate</returns>
|
||||
/// <returns>A readable/assignable <see cref="FieldRef{F}"/> delegate</returns>
|
||||
///
|
||||
public static FieldRef<F> StaticFieldRefAccess<F>(FieldInfo fieldInfo)
|
||||
{
|
||||
if (fieldInfo == null)
|
||||
if (fieldInfo is null)
|
||||
throw new ArgumentNullException(nameof(fieldInfo));
|
||||
var type = fieldInfo.DeclaringType;
|
||||
try
|
||||
{
|
||||
return StaticFieldRefAccessInternal<F>(fieldInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException($"StaticFieldRefAccess<{typeof(F)}> for {fieldInfo} caused an exception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
var s_name = $"__refget_{type?.Name ?? "null"}_static_fi_{fieldInfo.Name}";
|
||||
static FieldRef<F> StaticFieldRefAccessInternal<F>(FieldInfo fieldInfo)
|
||||
{
|
||||
if (fieldInfo.IsStatic is false)
|
||||
throw new ArgumentException("Field must be static");
|
||||
ValidateFieldType<F>(fieldInfo);
|
||||
|
||||
var dm = new DynamicMethodDefinition(s_name, typeof(F).MakeByRefType(), new Type[0]);
|
||||
var dm = new DynamicMethodDefinition($"__refget_{fieldInfo.DeclaringType?.Name ?? "null"}_static_fi_{fieldInfo.Name}",
|
||||
typeof(F).MakeByRefType(), new Type[0]);
|
||||
|
||||
var il = dm.GetILGenerator();
|
||||
il.Emit(OpCodes.Ldsflda, fieldInfo);
|
||||
@@ -945,6 +1304,22 @@ namespace HarmonyLib
|
||||
return (FieldRef<F>)dm.Generate().CreateDelegate(typeof(FieldRef<F>));
|
||||
}
|
||||
|
||||
static void ValidateFieldType<F>(FieldInfo fieldInfo)
|
||||
{
|
||||
var fieldType = fieldInfo.FieldType;
|
||||
if (fieldType.IsValueType)
|
||||
{
|
||||
// Boxing/unboxing is not allowed for ref values of value types.
|
||||
if (typeof(F) != fieldType)
|
||||
throw new ArgumentException("FieldRefAccess return type must be the same as FieldType for value types");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (typeof(F).IsAssignableFrom(fieldType) is false)
|
||||
throw new ArgumentException("FieldRefAccess return type must be assignable from FieldType for reference types");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a delegate to a given method</summary>
|
||||
/// <typeparam name="DelegateType">The delegate Type</typeparam>
|
||||
/// <param name="method">The method to create a delegate from.</param>
|
||||
|
||||
@@ -57,31 +57,30 @@ namespace HarmonyLibTests
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: AccessTools.StructFieldRefAccess
|
||||
//static IATestCase<T, F> ATestCase<T, F>(AccessTools.StructFieldRef<T, F> fieldRef) where T : struct
|
||||
//{
|
||||
// return new StructFieldRefTestCase<T, F>(fieldRef);
|
||||
//}
|
||||
static IATestCase<T, F> ATestCase<T, F>(AccessTools.StructFieldRef<T, F> fieldRef) where T : struct
|
||||
{
|
||||
return new StructFieldRefTestCase<T, F>(fieldRef);
|
||||
}
|
||||
|
||||
//class StructFieldRefTestCase<T, F> : IATestCase<T, F> where T : struct
|
||||
//{
|
||||
// readonly AccessTools.StructFieldRef<T, F> fieldRef;
|
||||
class StructFieldRefTestCase<T, F> : IATestCase<T, F> where T : struct
|
||||
{
|
||||
readonly AccessTools.StructFieldRef<T, F> fieldRef;
|
||||
|
||||
// public StructFieldRefTestCase(AccessTools.StructFieldRef<T, F> fieldRef)
|
||||
// {
|
||||
// this.fieldRef = fieldRef;
|
||||
// }
|
||||
public StructFieldRefTestCase(AccessTools.StructFieldRef<T, F> fieldRef)
|
||||
{
|
||||
this.fieldRef = fieldRef;
|
||||
}
|
||||
|
||||
// public F Get(ref T instance)
|
||||
// {
|
||||
// return fieldRef(ref instance);
|
||||
// }
|
||||
public F Get(ref T instance)
|
||||
{
|
||||
return fieldRef(ref instance);
|
||||
}
|
||||
|
||||
// public void Set(ref T instance, F value)
|
||||
// {
|
||||
// fieldRef(ref instance) = value;
|
||||
// }
|
||||
//}
|
||||
public void Set(ref T instance, F value)
|
||||
{
|
||||
fieldRef(ref instance) = value;
|
||||
}
|
||||
}
|
||||
|
||||
// AccessTools.StaticFieldRefAccess
|
||||
static IATestCase<T, F> ATestCase<T, F>(AccessTools.FieldRef<F> fieldRef)
|
||||
@@ -240,14 +239,6 @@ namespace HarmonyLibTests
|
||||
var value = testCase.Get(ref instance);
|
||||
// The ?.ToString() is a trick to ensure that value is fully evaluated from the ref value.
|
||||
_ = value?.ToString();
|
||||
// If the constraint is just Throws.Exception (rather than Throws.InstanceOf<ArgumentException), it means we expect potentially
|
||||
// undefined behavior. Depending on the environment, sometimes an exception (typically an InvalidProgramException) is thrown,
|
||||
// while sometimes an exception isn't thrown but the test case's get/set doesn't work correctly. In the latter case we can try
|
||||
// validating that value from the test case's get value matches the value from reflection GetValue.
|
||||
// TODO: Fix FieldRefAccess exception handling to always throw ArgumentException (or InvalidCastException when calling FieldRef
|
||||
// delegate with an incompatible instance) instead and remove this testing hack.
|
||||
if (TestTools.ThrowsConstraintExceptionType(resolvedConstraint) == typeof(Exception) && !Equals(origValue, value))
|
||||
throw new Exception("expected !Equals(origValue, value) (indicates invalid value)");
|
||||
testCase.Set(ref instance, value);
|
||||
}, expectedConstraint, testCaseLabel);
|
||||
}
|
||||
@@ -305,44 +296,29 @@ namespace HarmonyLibTests
|
||||
{
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = ATestCase<T, F>(instance => ref AccessTools.FieldRefAccess<T, F>(field)(instance)),
|
||||
["FieldRefAccess<T, F>(field)()"] = ATestCase<T, F>(instance => ref AccessTools.FieldRefAccess<T, F>(field)()),
|
||||
//["FieldRefAccess<T, F>(instance, field)"] = ATestCase<T, F>(instance => ref AccessTools.FieldRefAccess<T, F>(instance, field)), // TODO: implement this overload
|
||||
["FieldRefAccess<T, F>(instance, field)"] = ATestCase<T, F>(instance => ref AccessTools.FieldRefAccess<T, F>(instance, field)),
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Once generic class constraint is added to most FieldRefAccess methods, remove the calls that are no longer compilable.
|
||||
static Dictionary<string, IATestCase<T, F>> AvailableTestCases_FieldRefAccess_Struct_ByName<T, F>(
|
||||
string fieldName) where T : struct
|
||||
{
|
||||
return new Dictionary<string, IATestCase<T, F>>
|
||||
{
|
||||
["FieldRefAccess<T, F>(fieldName)(instance)"] = ATestCase<T, F>(instance => ref AccessTools.FieldRefAccess<T, F>(fieldName)(instance)),
|
||||
["FieldRefAccess<T, F>(instance, fieldName)"] = ATestCase<T, F>(instance => ref AccessTools.FieldRefAccess<T, F>(instance, fieldName)),
|
||||
["FieldRefAccess<F>(typeof(T), fieldName)(instance)"] = ATestCase<T, F>(instance => ref AccessTools.FieldRefAccess<F>(typeof(T), fieldName)(instance)),
|
||||
["FieldRefAccess<F>(typeof(T), fieldName)()"] = ATestCase<T, F>(instance => ref AccessTools.FieldRefAccess<F>(typeof(T), fieldName)()),
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Once generic class constraint is added to most FieldRefAccess methods, remove the calls that are no longer compilable.
|
||||
static Dictionary<string, IATestCase<T, F>> AvailableTestCases_FieldRefAccess_Struct_ByFieldInfo<T, F>(
|
||||
FieldInfo field) where T : struct
|
||||
{
|
||||
return new Dictionary<string, IATestCase<T, F>>
|
||||
{
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = ATestCase<T, F>(instance => ref AccessTools.FieldRefAccess<T, F>(field)(instance)),
|
||||
["FieldRefAccess<T, F>(field)()"] = ATestCase<T, F>(instance => ref AccessTools.FieldRefAccess<T, F>(field)()),
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: StructFieldRefAccess
|
||||
static Dictionary<string, IATestCase<T, F>> AvailableTestCases_StructFieldRefAccess<T, F>(FieldInfo field,
|
||||
string fieldName) where T : struct
|
||||
{
|
||||
return new Dictionary<string, IATestCase<T, F>>
|
||||
{
|
||||
//["StructFieldRefAccess<T, F>(fieldName)(ref instance)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess<T, F>(fieldName)(ref instance)),
|
||||
//["StructFieldRefAccess<T, F>(ref instance, fieldName)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess<T, F>(ref instance, fieldName)),
|
||||
//["StructFieldRefAccess<T, F>(field)(ref instance)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess<T, F>(field)(ref instance)),
|
||||
//["StructFieldRefAccess<T, F>(ref instance, field)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess<T, F>(ref instance, field)),
|
||||
["StructFieldRefAccess<T, F>(fieldName)(ref instance)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess<T, F>(fieldName)(ref instance)),
|
||||
["StructFieldRefAccess<T, F>(ref instance, fieldName)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess<T, F>(ref instance, fieldName)),
|
||||
["StructFieldRefAccess<T, F>(field)(ref instance)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess<T, F>(field)(ref instance)),
|
||||
["StructFieldRefAccess<T, F>(ref instance, field)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess<T, F>(ref instance, field)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -383,7 +359,6 @@ namespace HarmonyLibTests
|
||||
var availableTestCases = Merge(
|
||||
AvailableTestCases_StructFieldRefAccess<T, F>(field, field.Name),
|
||||
AvailableTestCases_FieldRefAccess_Struct_ByName<T, F>(field.Name),
|
||||
AvailableTestCases_FieldRefAccess_Struct_ByFieldInfo<T, F>(field),
|
||||
AvailableTestCases_StaticFieldRefAccess_ByName<T, F>(field.Name),
|
||||
AvailableTestCases_StaticFieldRefAccess_ByFieldInfo<T, F>(field));
|
||||
new ATestSuite<T, F>(typeof(T), field, testValue, expectedCaseToConstraint, availableTestCases).Run();
|
||||
@@ -404,36 +379,12 @@ namespace HarmonyLibTests
|
||||
return newExpectedCaseToConstraint;
|
||||
}
|
||||
|
||||
// TODO: This shouldn't exist - public fields should be treated equivalently as private fields.
|
||||
static Dictionary<string, ReusableConstraint> PublicField(Dictionary<string, ReusableConstraint> expectedCaseToConstraint)
|
||||
{
|
||||
return expectedCaseToConstraint.Merge(ReusableConstraints(new Dictionary<string, IResolveConstraint>
|
||||
{
|
||||
["FieldRefAccess<T, F>(fieldName)(instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<T, F>(instance, fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StaticFieldRefAccess<T, F>(fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StaticFieldRefAccess<F>(typeof(T), fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
}).Where(pair => expectedCaseToConstraint.ContainsKey(pair.Key)));
|
||||
}
|
||||
|
||||
// TODO: This shouldn't exist - FieldRefAccess's T=object special-casing should be generalized to handle any type assignable from field's declaring type.
|
||||
// Only FieldMissingOnTypeT should be used when T is an interface type.
|
||||
static Dictionary<string, ReusableConstraint> InterfaceT(Dictionary<string, ReusableConstraint> expectedCaseToConstraint)
|
||||
{
|
||||
return expectedCaseToConstraint.Merge(ReusableConstraints(new Dictionary<string, IResolveConstraint>
|
||||
{
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = Throws.Exception,
|
||||
["FieldRefAccess<T, F>(field)()"] = Throws.Exception,
|
||||
}).Where(pair => expectedCaseToConstraint.ContainsKey(pair.Key)));
|
||||
}
|
||||
|
||||
static Dictionary<string, ReusableConstraint> FieldMissingOnTypeT(Dictionary<string, ReusableConstraint> expectedCaseToConstraint)
|
||||
{
|
||||
return expectedCaseToConstraint.Merge(ReusableConstraints(new Dictionary<string, IResolveConstraint>
|
||||
{
|
||||
// TODO: StructFieldRefAccess
|
||||
//["StructFieldRefAccess<T, F>(fieldName)(ref instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
//["StructFieldRefAccess<T, F>(ref instance, fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StructFieldRefAccess<T, F>(fieldName)(ref instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StructFieldRefAccess<T, F>(ref instance, fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<T, F>(fieldName)(instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<T, F>(instance, fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<F>(typeof(T), fieldName)(instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
@@ -447,14 +398,21 @@ namespace HarmonyLibTests
|
||||
{
|
||||
// Given that type T must be assignable from instance type, and that instance type is incompatible with field's declaring type,
|
||||
// assume that the field cannot be found on type T.
|
||||
return FieldMissingOnTypeT(expectedCaseToConstraint).Merge(ReusableConstraints(new Dictionary<string, IResolveConstraint>
|
||||
var newExpectedCaseToConstraint = FieldMissingOnTypeT(expectedCaseToConstraint).Merge(ReusableConstraints(new Dictionary<string, IResolveConstraint>
|
||||
{
|
||||
// TODO: StructFieldRefAccess
|
||||
//["StructFieldRefAccess<T, F>(field)(ref instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
//["StructFieldRefAccess<T, F>(ref instance, field)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = SkipTest("incompatible instance type can cause crash"), // TODO: should be InvalidCastException if not already another Throws constraint
|
||||
//["FieldRefAccess<T, F>(instance, field)"] = Throws.InstanceOf<ArgumentException>(), // TODO: implement this overload
|
||||
["StructFieldRefAccess<T, F>(field)(ref instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StructFieldRefAccess<T, F>(ref instance, field)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<T, F>(instance, field)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
}).Where(pair => expectedCaseToConstraint.ContainsKey(pair.Key)));
|
||||
// Only override Throws.Nothing constraint with InvalidCastException for these test cases,
|
||||
// since other Throws constraints should have precedence over InvalidCastException:
|
||||
// - ArgumentException is only thrown from FieldRefAccess
|
||||
// - NullReferenceException is only thrown when invoking FieldRefAccess-returned delegate with null instance
|
||||
// - InvalidCastException is only thrown when invoking FieldRefAccess-returned delegate with an instance of incompatible type
|
||||
return newExpectedCaseToConstraint.Merge(ReusableConstraints(new Dictionary<string, IResolveConstraint>
|
||||
{
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = Throws.TypeOf<InvalidCastException>(),
|
||||
}).Where(pair => expectedCaseToConstraint.TryGetValue(pair.Key, out var constraint) && constraint.Resolve() is ThrowsNothingConstraint));
|
||||
}
|
||||
|
||||
static Dictionary<string, ReusableConstraint> IncompatibleTypeT(Dictionary<string, ReusableConstraint> expectedCaseToConstraint)
|
||||
@@ -464,45 +422,8 @@ namespace HarmonyLibTests
|
||||
// Also assume that the field cannot be found on type T (already assumed in IncompatibleInstanceType).
|
||||
return IncompatibleInstanceType(expectedCaseToConstraint).Merge(ReusableConstraints(new Dictionary<string, IResolveConstraint>
|
||||
{
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = Throws.Exception, // TODO: should be ArgumentException
|
||||
["FieldRefAccess<T, F>(field)()"] = Throws.Exception, // TODO: should be ArgumentException
|
||||
}).Where(pair => expectedCaseToConstraint.ContainsKey(pair.Key)));
|
||||
}
|
||||
|
||||
// TODO: This shouldn't exist - FieldRefAccess should ignore T for static fields.
|
||||
static Dictionary<string, ReusableConstraint> StaticIncompatibleTypeT(Dictionary<string, ReusableConstraint> expectedCaseToConstraint)
|
||||
{
|
||||
return expectedCaseToConstraint.Merge(ReusableConstraints(new Dictionary<string, IResolveConstraint>
|
||||
{
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = Throws.Exception,
|
||||
["FieldRefAccess<T, F>(field)()"] = Throws.Exception,
|
||||
}).Where(pair => expectedCaseToConstraint.ContainsKey(pair.Key)));
|
||||
}
|
||||
|
||||
// TODO: This shouldn't exist - FieldRefAccess should be using AccessTools.Field for field search.
|
||||
// For static and non-protected / public / internal-and-same-assembly instance fields declared in parent classes.
|
||||
static Dictionary<string, ReusableConstraint> FieldNotInheritedBySubClass(Dictionary<string, ReusableConstraint> expectedCaseToConstraint)
|
||||
{
|
||||
return expectedCaseToConstraint.Merge(ReusableConstraints(new Dictionary<string, IResolveConstraint>
|
||||
{
|
||||
// Following search for only declared fields (excludes all fields from parents).
|
||||
["FieldRefAccess<T, F>(fieldName)(instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<T, F>(instance, fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StaticFieldRefAccess<T, F>(fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StaticFieldRefAccess<F>(typeof(T), fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
}).Where(pair => expectedCaseToConstraint.ContainsKey(pair.Key)));
|
||||
}
|
||||
|
||||
// TODO: This shouldn't exist - FieldRefAccess should be using AccessTools.Field for field search.
|
||||
// For public / protected / internal-and-same-assembly instance fields declared in parent classes.
|
||||
static Dictionary<string, ReusableConstraint> FieldInheritedBySubClass(Dictionary<string, ReusableConstraint> expectedCaseToConstraint)
|
||||
{
|
||||
return expectedCaseToConstraint.Merge(ReusableConstraints(new Dictionary<string, IResolveConstraint>
|
||||
{
|
||||
// Following search for only declared fields (excludes all fields from parents).
|
||||
// This doesn't include StaticFieldRefAccess since FieldNotInheritedBySubClass should always be used instead for such fields.
|
||||
["FieldRefAccess<T, F>(fieldName)(instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<T, F>(instance, fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<T, F>(field)()"] = Throws.InstanceOf<ArgumentException>(),
|
||||
}).Where(pair => expectedCaseToConstraint.ContainsKey(pair.Key)));
|
||||
}
|
||||
|
||||
@@ -517,11 +438,7 @@ namespace HarmonyLibTests
|
||||
continue;
|
||||
var expectedExceptionType = TestTools.ThrowsConstraintExceptionType(expectedConstraint);
|
||||
if (expectedExceptionType is null || expectedExceptionType == typeof(NullReferenceException))
|
||||
{
|
||||
// TODO: StaticFieldRefAccess should throw ArgumentException just like FieldRefAccess.
|
||||
newExpectedCaseToConstraint[testCaseName] = new ReusableConstraint(Throws.InstanceOf(
|
||||
testCaseName.StartsWith("StaticFieldRefAccess") ? typeof(IncompatibleFieldTypeException) : typeof(ArgumentException)));
|
||||
}
|
||||
newExpectedCaseToConstraint[testCaseName] = new ReusableConstraint(Throws.InstanceOf<ArgumentException>());
|
||||
}
|
||||
return newExpectedCaseToConstraint;
|
||||
}
|
||||
@@ -535,11 +452,11 @@ namespace HarmonyLibTests
|
||||
["FieldRefAccess<F>(typeof(T), fieldName)()"] = Throws.TypeOf<NullReferenceException>(),
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = Throws.Nothing,
|
||||
["FieldRefAccess<T, F>(field)()"] = Throws.TypeOf<NullReferenceException>(),
|
||||
//["FieldRefAccess<T, F>(instance, field)"] = Throws.Nothing, // TODO: implement this overload
|
||||
["FieldRefAccess<T, F>(instance, field)"] = Throws.Nothing,
|
||||
["StaticFieldRefAccess<T, F>(fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StaticFieldRefAccess<F>(typeof(T), fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StaticFieldRefAccess<F>(field)()"] = Throws.Exception, // TODO: should be ArgumentException
|
||||
["StaticFieldRefAccess<T, F>(field)"] = Throws.Exception, // TODO: should be ArgumentException
|
||||
["StaticFieldRefAccess<F>(field)()"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StaticFieldRefAccess<T, F>(field)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
});
|
||||
|
||||
static readonly Dictionary<string, ReusableConstraint> expectedCaseToConstraint_ClassStatic =
|
||||
@@ -551,7 +468,7 @@ namespace HarmonyLibTests
|
||||
["FieldRefAccess<F>(typeof(T), fieldName)()"] = Throws.Nothing,
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = Throws.Nothing, // T is ignored
|
||||
["FieldRefAccess<T, F>(field)()"] = Throws.Nothing, // T is ignored
|
||||
//["FieldRefAccess<T, F>(instance, field)"] = Throws.InstanceOf<ArgumentException>(), // TODO: implement this overload
|
||||
["FieldRefAccess<T, F>(instance, field)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StaticFieldRefAccess<T, F>(fieldName)"] = Throws.Nothing,
|
||||
["StaticFieldRefAccess<F>(typeof(T), fieldName)"] = Throws.Nothing,
|
||||
["StaticFieldRefAccess<F>(field)()"] = Throws.Nothing,
|
||||
@@ -561,41 +478,27 @@ namespace HarmonyLibTests
|
||||
static readonly Dictionary<string, ReusableConstraint> expectedCaseToConstraint_StructInstance =
|
||||
ReusableConstraints(new Dictionary<string, IResolveConstraint>
|
||||
{
|
||||
// TODO: StructFieldRefAccess
|
||||
//["StructFieldRefAccess<T, F>(fieldName)(ref instance)"] = Throws.Nothing,
|
||||
//["StructFieldRefAccess<T, F>(ref instance, fieldName)"] = Throws.Nothing,
|
||||
//["StructFieldRefAccess<T, F>(field)(ref instance)"] = Throws.Nothing,
|
||||
//["StructFieldRefAccess<T, F>(ref instance, field)"] = Throws.Nothing,
|
||||
["FieldRefAccess<T, F>(fieldName)(instance)"] = SkipTest("struct instance can cause crash"), // TODO: will be non-compilable due to class constraint
|
||||
["FieldRefAccess<T, F>(instance, fieldName)"] = SkipTest("struct instance can cause crash"), // TODO: will be non-compilable due to class constraint
|
||||
["FieldRefAccess<F>(typeof(T), fieldName)(instance)"] = SkipTest("struct instance can cause crash"), // TODO: should be ArgumentException
|
||||
["FieldRefAccess<F>(typeof(T), fieldName)()"] = Throws.TypeOf<NullReferenceException>(), // TODO: should be ArgumentException
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = SkipTest("struct instance can cause crash"), // TODO: will be non-compilable due to class constraint
|
||||
["FieldRefAccess<T, F>(field)()"] = SkipTest("struct instance can cause crash"), // TODO: will be non-compilable due to class constraint
|
||||
["StructFieldRefAccess<T, F>(fieldName)(ref instance)"] = Throws.Nothing,
|
||||
["StructFieldRefAccess<T, F>(ref instance, fieldName)"] = Throws.Nothing,
|
||||
["StructFieldRefAccess<T, F>(field)(ref instance)"] = Throws.Nothing,
|
||||
["StructFieldRefAccess<T, F>(ref instance, field)"] = Throws.Nothing,
|
||||
["FieldRefAccess<F>(typeof(T), fieldName)(instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<F>(typeof(T), fieldName)()"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StaticFieldRefAccess<T, F>(fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StaticFieldRefAccess<F>(typeof(T), fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StaticFieldRefAccess<F>(field)()"] = SkipTest("struct instance can cause crash"), // TODO: should be ArgumentException
|
||||
["StaticFieldRefAccess<T, F>(field)"] = SkipTest("struct instance can cause crash"), // TODO: should be ArgumentException
|
||||
["StaticFieldRefAccess<F>(field)()"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StaticFieldRefAccess<T, F>(field)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
});
|
||||
|
||||
// TODO: This shouldn't need to exist.
|
||||
static IResolveConstraint MonoThrowsException => AccessTools.IsMonoRuntime ?
|
||||
(IResolveConstraint)Throws.Exception : Throws.Nothing;
|
||||
|
||||
static readonly Dictionary<string, ReusableConstraint> expectedCaseToConstraint_StructStatic =
|
||||
ReusableConstraints(new Dictionary<string, IResolveConstraint>
|
||||
{
|
||||
// TODO: StructFieldRefAccess
|
||||
//["StructFieldRefAccess<T, F>(fieldName)(ref instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
//["StructFieldRefAccess<T, F>(ref instance, fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
//["StructFieldRefAccess<T, F>(field)(ref instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
//["StructFieldRefAccess<T, F>(ref instance, field)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<T, F>(fieldName)(instance)"] = Throws.InstanceOf<ArgumentException>(), // TODO: will be non-compilable due to class constraint
|
||||
["FieldRefAccess<T, F>(instance, fieldName)"] = Throws.InstanceOf<ArgumentException>(), // TODO: will be non-compilable due to class constraint
|
||||
["StructFieldRefAccess<T, F>(fieldName)(ref instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StructFieldRefAccess<T, F>(ref instance, fieldName)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StructFieldRefAccess<T, F>(field)(ref instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["StructFieldRefAccess<T, F>(ref instance, field)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<F>(typeof(T), fieldName)(instance)"] = Throws.Nothing,
|
||||
["FieldRefAccess<F>(typeof(T), fieldName)()"] = Throws.Nothing,
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = MonoThrowsException, // TODO: will be non-compilable due to class constraint
|
||||
["FieldRefAccess<T, F>(field)()"] = MonoThrowsException, // TODO: will be non-compilable due to class constraint
|
||||
["StaticFieldRefAccess<T, F>(fieldName)"] = Throws.Nothing,
|
||||
["StaticFieldRefAccess<F>(typeof(T), fieldName)"] = Throws.Nothing,
|
||||
["StaticFieldRefAccess<F>(field)()"] = Throws.Nothing,
|
||||
@@ -607,17 +510,15 @@ namespace HarmonyLibTests
|
||||
IncompatibleTypeT(expectedCaseToConstraint_StructInstance);
|
||||
|
||||
static readonly Dictionary<string, ReusableConstraint> expectedCaseToConstraint_ClassStatic_StructT =
|
||||
StaticIncompatibleTypeT(FieldMissingOnTypeT(expectedCaseToConstraint_StructStatic));
|
||||
FieldMissingOnTypeT(expectedCaseToConstraint_StructStatic);
|
||||
|
||||
// AccessToolsStruct is compatible with object/ValueType/IAccessToolsType reference types (classes/interfaces), so NOT using IncompatibleTypeT here.
|
||||
static readonly Dictionary<string, ReusableConstraint> expectedCaseToConstraint_StructInstance_ClassT =
|
||||
FieldMissingOnTypeT(expectedCaseToConstraint_ClassInstance).Merge(ReusableConstraints(new Dictionary<string, IResolveConstraint>
|
||||
{
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = SkipTest("struct instance can cause crash"), // TODO: should be ArgumentException
|
||||
["FieldRefAccess<T, F>(field)()"] = Throws.TypeOf<NullReferenceException>(), // TODO: should be ArgumentException
|
||||
//["FieldRefAccess<T, F>(instance, field)"] = Throws.InstanceOf<ArgumentException>(), // TODO: implement this overload
|
||||
["StaticFieldRefAccess<F>(field)()"] = SkipTest("struct instance can cause crash"), // TODO: should be ArgumentException
|
||||
["StaticFieldRefAccess<T, F>(field)"] = SkipTest("struct instance can cause crash"), // TODO: should be ArgumentException
|
||||
["FieldRefAccess<T, F>(field)(instance)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<T, F>(field)()"] = Throws.InstanceOf<ArgumentException>(),
|
||||
["FieldRefAccess<T, F>(instance, field)"] = Throws.InstanceOf<ArgumentException>(),
|
||||
}));
|
||||
|
||||
static readonly Dictionary<string, ReusableConstraint> expectedCaseToConstraint_StructStatic_ClassT =
|
||||
@@ -633,11 +534,11 @@ namespace HarmonyLibTests
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, string>(
|
||||
field, "field1test", expectedCaseToConstraint);
|
||||
TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, string>(
|
||||
field, "field1test", FieldInheritedBySubClass(expectedCaseToConstraint));
|
||||
field, "field1test", expectedCaseToConstraint);
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsSubClass, string>(
|
||||
field, "field1test", expectedCaseToConstraint);
|
||||
TestSuite_Class<IAccessToolsType, AccessToolsClass, string>(
|
||||
field, "field1test", InterfaceT(FieldMissingOnTypeT(expectedCaseToConstraint)));
|
||||
field, "field1test", FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<object, AccessToolsClass, string>(
|
||||
field, "field1test", FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<object, string, string>(
|
||||
@@ -652,10 +553,8 @@ namespace HarmonyLibTests
|
||||
field, "field1test", expectedCaseToConstraint);
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, string[]>(
|
||||
field, new[] { "should always throw" }, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
// TODO: Following tests will either fail to get/set correctly or crash the runtime due to invalid IL code causing some fatal error.
|
||||
// Fix StaticFieldRefAccess to consistently throw ArgumentException when field type is incompatible with F.
|
||||
//TestSuite_Class<AccessToolsClass, AccessToolsClass, int>(
|
||||
// field, 1337, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, int>(
|
||||
field, 1337, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -665,15 +564,15 @@ namespace HarmonyLibTests
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
var field = AccessTools.Field(typeof(AccessToolsClass), "field2");
|
||||
var expectedCaseToConstraint = PublicField(expectedCaseToConstraint_ClassInstance);
|
||||
var expectedCaseToConstraint = expectedCaseToConstraint_ClassInstance;
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, float>(
|
||||
field, 314f, expectedCaseToConstraint);
|
||||
TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, float>(
|
||||
field, 314f, FieldInheritedBySubClass(expectedCaseToConstraint));
|
||||
field, 314f, expectedCaseToConstraint);
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsSubClass, float>(
|
||||
field, 314f, expectedCaseToConstraint);
|
||||
TestSuite_Class<IAccessToolsType, AccessToolsClass, float>(
|
||||
field, 314f, InterfaceT(FieldMissingOnTypeT(expectedCaseToConstraint)));
|
||||
field, 314f, FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<object, AccessToolsClass, float>(
|
||||
field, 314f, FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<object, string, float>(
|
||||
@@ -682,18 +581,16 @@ namespace HarmonyLibTests
|
||||
field, 314f, IncompatibleTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Struct<int, float>(
|
||||
field, 314f, expectedCaseToConstraint_ClassInstance_StructT);
|
||||
// TODO: Following tests will either fail to get/set correctly or crash the runtime due to invalid IL code causing some fatal error.
|
||||
// Fix *FieldRefAccess to consistently throw ArgumentException when field type is a value type and F is a different type.
|
||||
//TestSuite_Class<AccessToolsClass, AccessToolsClass, object>(
|
||||
// field, 314f, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Class<AccessToolsClass, AccessToolsClass, ValueType>(
|
||||
// field, 314f, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Class<AccessToolsClass, AccessToolsClass, float?>(
|
||||
// field, 314f, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Class<AccessToolsClass, AccessToolsClass, IComparable>(
|
||||
// field, 314f, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Class<AccessToolsClass, AccessToolsClass, double>(
|
||||
// field, 314f, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, object>(
|
||||
field, 314f, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, ValueType>(
|
||||
field, 314f, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, float?>(
|
||||
field, 314f, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, IComparable>(
|
||||
field, 314f, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, double>(
|
||||
field, 314f, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -703,34 +600,32 @@ namespace HarmonyLibTests
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
var field = AccessTools.Field(typeof(AccessToolsClass), "field3");
|
||||
var expectedCaseToConstraint = PublicField(expectedCaseToConstraint_ClassStatic);
|
||||
var expectedCaseToConstraint = expectedCaseToConstraint_ClassStatic;
|
||||
// Note: As this is as static field, instance type is ignored, so IncompatibleInstanceType is never needed.
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, long>(
|
||||
field, 314L, expectedCaseToConstraint);
|
||||
TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, long>(
|
||||
field, 314L, FieldNotInheritedBySubClass(expectedCaseToConstraint));
|
||||
field, 314L, expectedCaseToConstraint);
|
||||
TestSuite_Class<IAccessToolsType, AccessToolsClass, long>(
|
||||
field, 314L, InterfaceT(FieldMissingOnTypeT(expectedCaseToConstraint)));
|
||||
field, 314L, FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<object, AccessToolsClass, long>(
|
||||
field, 314L, FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<object, string, long>(
|
||||
field, 314L, FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<string, string, long>(
|
||||
field, 314L, StaticIncompatibleTypeT(FieldMissingOnTypeT(expectedCaseToConstraint)));
|
||||
field, 314L, FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Struct<int, long>(
|
||||
field, 314L, PublicField(expectedCaseToConstraint_ClassStatic_StructT));
|
||||
// TODO: Following tests will either fail to get/set correctly or crash the runtime due to invalid IL code causing some fatal error.
|
||||
// Fix *FieldRefAccess to consistently throw ArgumentException when field type is a value type and F is a different type.
|
||||
//TestSuite_Class<AccessToolsClass, AccessToolsClass, object>(
|
||||
// field, 314L, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Class<AccessToolsClass, AccessToolsClass, ValueType>(
|
||||
// field, 314L, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Class<AccessToolsClass, AccessToolsClass, long?>(
|
||||
// field, 314L, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Class<AccessToolsClass, AccessToolsClass, IComparable>(
|
||||
// field, 314L, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Class<AccessToolsClass, AccessToolsClass, double>(
|
||||
// field, 314L, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
field, 314L, expectedCaseToConstraint_ClassStatic_StructT);
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, object>(
|
||||
field, 314L, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, ValueType>(
|
||||
field, 314L, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, long?>(
|
||||
field, 314L, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, IComparable>(
|
||||
field, 314L, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, double>(
|
||||
field, 314L, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -745,15 +640,15 @@ namespace HarmonyLibTests
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, string>(
|
||||
field, "field4test", expectedCaseToConstraint);
|
||||
TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, string>(
|
||||
field, "field4test", FieldNotInheritedBySubClass(expectedCaseToConstraint));
|
||||
field, "field4test", expectedCaseToConstraint);
|
||||
TestSuite_Class<IAccessToolsType, AccessToolsClass, string>(
|
||||
field, "field4test", InterfaceT(FieldMissingOnTypeT(expectedCaseToConstraint)));
|
||||
field, "field4test", FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<object, AccessToolsClass, string>(
|
||||
field, "field4test", FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<object, string, string>(
|
||||
field, "field4test", FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<string, string, string>(
|
||||
field, "field4test", StaticIncompatibleTypeT(FieldMissingOnTypeT(expectedCaseToConstraint)));
|
||||
field, "field4test", FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Struct<int, string>(
|
||||
field, "field4test", expectedCaseToConstraint_ClassStatic_StructT);
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, object>(
|
||||
@@ -762,10 +657,8 @@ namespace HarmonyLibTests
|
||||
field, "field4test", expectedCaseToConstraint);
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, IEnumerable<string>>(
|
||||
field, new[] { "should always throw" }, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
// TODO: Following tests will either fail to get/set correctly or crash the runtime due to invalid IL code causing some fatal error.
|
||||
// Fix StaticFieldRefAccess to consistently throw ArgumentException when field type is incompatible with F.
|
||||
//TestSuite_Class<AccessToolsClass, AccessToolsClass, int>(
|
||||
// field, 1337, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, int>(
|
||||
field, 1337, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -776,14 +669,14 @@ namespace HarmonyLibTests
|
||||
{
|
||||
var field = AccessTools.Field(typeof(AccessToolsSubClass), "subclassField1");
|
||||
var expectedCaseToConstraint = expectedCaseToConstraint_ClassInstance;
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, string>( // TODO: should be IncompatibleInstanceType once T=object special-casing is generalized
|
||||
field, "subclassField1test", IncompatibleTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, string>(
|
||||
field, "subclassField1test", IncompatibleInstanceType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, string>(
|
||||
field, "subclassField1test", expectedCaseToConstraint);
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsSubClass, string>( // TODO: should be FieldMissingOnTypeT once T=object special-casing is generalized
|
||||
field, "subclassField1test", IncompatibleTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsSubClass, string>(
|
||||
field, "subclassField1test", FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<IAccessToolsType, AccessToolsSubClass, string>(
|
||||
field, "subclassField1test", InterfaceT(FieldMissingOnTypeT(expectedCaseToConstraint)));
|
||||
field, "subclassField1test", FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<object, AccessToolsSubClass, string>(
|
||||
field, "subclassField1test", FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<object, AccessToolsClass, string>(
|
||||
@@ -812,11 +705,11 @@ namespace HarmonyLibTests
|
||||
var expectedCaseToConstraint = expectedCaseToConstraint_ClassStatic;
|
||||
// Note: As this is as static field, instance type is ignored, so IncompatibleInstanceType is never needed.
|
||||
TestSuite_Class<AccessToolsClass, AccessToolsClass, int>(
|
||||
field, 123, StaticIncompatibleTypeT(FieldMissingOnTypeT(expectedCaseToConstraint)));
|
||||
field, 123, FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, int>(
|
||||
field, 123, expectedCaseToConstraint);
|
||||
TestSuite_Class<IAccessToolsType, AccessToolsSubClass, int>(
|
||||
field, 123, InterfaceT(FieldMissingOnTypeT(expectedCaseToConstraint)));
|
||||
field, 123, FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<object, AccessToolsSubClass, int>(
|
||||
field, 123, FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<object, AccessToolsClass, int>(
|
||||
@@ -824,21 +717,19 @@ namespace HarmonyLibTests
|
||||
TestSuite_Class<object, string, int>(
|
||||
field, 123, FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Class<string, string, int>(
|
||||
field, 123, StaticIncompatibleTypeT(FieldMissingOnTypeT(expectedCaseToConstraint)));
|
||||
field, 123, FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Struct<int, int>(
|
||||
field, 123, expectedCaseToConstraint_ClassStatic_StructT);
|
||||
// TODO: Following tests will either fail to get/set correctly or crash the runtime due to invalid IL code causing some fatal error.
|
||||
// Fix *FieldRefAccess to consistently throw ArgumentException when field type is a value type and F is a different type.
|
||||
//TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, object>(
|
||||
// field, 123, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, ValueType>(
|
||||
// field, 123, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, int?>(
|
||||
// field, 123, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, IComparable>(
|
||||
// field, 123, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, double>(
|
||||
// field, 123, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, object>(
|
||||
field, 123, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, ValueType>(
|
||||
field, 123, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, int?>(
|
||||
field, 123, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, IComparable>(
|
||||
field, 123, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Class<AccessToolsSubClass, AccessToolsSubClass, double>(
|
||||
field, 123, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -848,12 +739,12 @@ namespace HarmonyLibTests
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
var field = AccessTools.Field(typeof(AccessToolsStruct), "structField1");
|
||||
var expectedCaseToConstraint = PublicField(expectedCaseToConstraint_StructInstance);
|
||||
var expectedCaseToConstraint = expectedCaseToConstraint_StructInstance;
|
||||
var expectedCaseToConstraintClassT = expectedCaseToConstraint_StructInstance_ClassT;
|
||||
TestSuite_Struct<AccessToolsStruct, string>(
|
||||
field, "structField1test", expectedCaseToConstraint);
|
||||
TestSuite_Class<IAccessToolsType, AccessToolsStruct, string>(
|
||||
field, "structField1test", InterfaceT(expectedCaseToConstraintClassT));
|
||||
field, "structField1test", expectedCaseToConstraintClassT);
|
||||
TestSuite_Class<object, AccessToolsStruct, string>(
|
||||
field, "structField1test", expectedCaseToConstraintClassT);
|
||||
TestSuite_Class<object, string, string>(
|
||||
@@ -882,7 +773,7 @@ namespace HarmonyLibTests
|
||||
TestSuite_Struct<AccessToolsStruct, int>(
|
||||
field, 1234, expectedCaseToConstraint);
|
||||
TestSuite_Class<IAccessToolsType, AccessToolsStruct, int>(
|
||||
field, 1234, InterfaceT(expectedCaseToConstraintClassT));
|
||||
field, 1234, expectedCaseToConstraintClassT);
|
||||
TestSuite_Class<object, AccessToolsStruct, int>(
|
||||
field, 1234, expectedCaseToConstraintClassT);
|
||||
TestSuite_Class<object, string, int>(
|
||||
@@ -891,18 +782,16 @@ namespace HarmonyLibTests
|
||||
field, 1234, IncompatibleTypeT(expectedCaseToConstraintClassT));
|
||||
TestSuite_Struct<int, int>(
|
||||
field, 1234, IncompatibleTypeT(expectedCaseToConstraint));
|
||||
// TODO: Following tests will either fail to get/set correctly or crash the runtime due to invalid IL code causing some fatal error.
|
||||
// Fix *FieldRefAccess to consistently throw ArgumentException when field type is a value type and F is a different type.
|
||||
//TestSuite_Struct<AccessToolsStruct, object>(
|
||||
// field, 1234, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Struct<AccessToolsStruct, ValueType>(
|
||||
// field, 1234, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Struct<AccessToolsStruct, int?>(
|
||||
// field, 1234, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Struct<AccessToolsStruct, IComparable>(
|
||||
// field, 1234, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Struct<AccessToolsStruct, long>(
|
||||
// field, 1234, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Struct<AccessToolsStruct, object>(
|
||||
field, 1234, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Struct<AccessToolsStruct, ValueType>(
|
||||
field, 1234, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Struct<AccessToolsStruct, int?>(
|
||||
field, 1234, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Struct<AccessToolsStruct, IComparable>(
|
||||
field, 1234, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Struct<AccessToolsStruct, long>(
|
||||
field, 1234, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -918,27 +807,25 @@ namespace HarmonyLibTests
|
||||
TestSuite_Struct<AccessToolsStruct, int>(
|
||||
field, 4321, expectedCaseToConstraint);
|
||||
TestSuite_Class<IAccessToolsType, AccessToolsStruct, int>(
|
||||
field, 4321, InterfaceT(expectedCaseToConstraintClassT));
|
||||
field, 4321, expectedCaseToConstraintClassT);
|
||||
TestSuite_Class<object, AccessToolsStruct, int>(
|
||||
field, 4321, expectedCaseToConstraintClassT);
|
||||
TestSuite_Class<object, string, int>(
|
||||
field, 4321, expectedCaseToConstraintClassT);
|
||||
TestSuite_Class<string, string, int>(
|
||||
field, 4321, StaticIncompatibleTypeT(FieldMissingOnTypeT(expectedCaseToConstraintClassT)));
|
||||
field, 4321, FieldMissingOnTypeT(expectedCaseToConstraintClassT));
|
||||
TestSuite_Struct<int, int>(
|
||||
field, 4321, StaticIncompatibleTypeT(FieldMissingOnTypeT(expectedCaseToConstraint)));
|
||||
// TODO: Following tests will either fail to get/set correctly or crash the runtime due to invalid IL code causing some fatal error.
|
||||
// Fix StaticFieldRefAccess to consistently throw ArgumentException when field type is incompatible with F.
|
||||
//TestSuite_Struct<AccessToolsStruct, object>(
|
||||
// field, 4321, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Struct<AccessToolsStruct, ValueType>(
|
||||
// field, 4321, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Struct<AccessToolsStruct, int?>(
|
||||
// field, 4321, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Struct<AccessToolsStruct, IComparable>(
|
||||
// field, 4321, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
//TestSuite_Struct<AccessToolsStruct, long>(
|
||||
// field, 4321, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
field, 4321, FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Struct<AccessToolsStruct, object>(
|
||||
field, 4321, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Struct<AccessToolsStruct, ValueType>(
|
||||
field, 4321, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Struct<AccessToolsStruct, int?>(
|
||||
field, 4321, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Struct<AccessToolsStruct, IComparable>(
|
||||
field, 4321, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Struct<AccessToolsStruct, long>(
|
||||
field, 4321, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -948,126 +835,28 @@ namespace HarmonyLibTests
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
var field = AccessTools.Field(typeof(AccessToolsStruct), "structField4");
|
||||
var expectedCaseToConstraint = PublicField(expectedCaseToConstraint_StructStatic);
|
||||
var expectedCaseToConstraintClassT = PublicField(expectedCaseToConstraint_StructStatic_ClassT);
|
||||
var expectedCaseToConstraint = expectedCaseToConstraint_StructStatic;
|
||||
var expectedCaseToConstraintClassT = expectedCaseToConstraint_StructStatic_ClassT;
|
||||
// Note: As this is as static field, instance type is ignored, so IncompatibleInstanceType is never needed.
|
||||
TestSuite_Struct<AccessToolsStruct, string>(
|
||||
field, "structField4test", expectedCaseToConstraint);
|
||||
TestSuite_Class<IAccessToolsType, AccessToolsStruct, string>(
|
||||
field, "structField4test", InterfaceT(expectedCaseToConstraintClassT));
|
||||
field, "structField4test", expectedCaseToConstraintClassT);
|
||||
TestSuite_Class<object, AccessToolsStruct, string>(
|
||||
field, "structField4test", expectedCaseToConstraintClassT);
|
||||
TestSuite_Class<object, string, string>(
|
||||
field, "structField4test", expectedCaseToConstraintClassT);
|
||||
TestSuite_Class<string, string, string>(
|
||||
field, "structField4test", StaticIncompatibleTypeT(FieldMissingOnTypeT(expectedCaseToConstraintClassT)));
|
||||
field, "structField4test", FieldMissingOnTypeT(expectedCaseToConstraintClassT));
|
||||
TestSuite_Struct<int, string>(
|
||||
field, "structField4test", StaticIncompatibleTypeT(FieldMissingOnTypeT(expectedCaseToConstraint)));
|
||||
field, "structField4test", FieldMissingOnTypeT(expectedCaseToConstraint));
|
||||
TestSuite_Struct<AccessToolsStruct, object>(
|
||||
field, "structField4test", expectedCaseToConstraint);
|
||||
TestSuite_Struct<AccessToolsStruct, IComparable>(
|
||||
field, "structField4test", expectedCaseToConstraint);
|
||||
// TODO: Following tests will either fail to get/set correctly or crash the runtime due to invalid IL code causing some fatal error.
|
||||
// Fix StaticFieldRefAccess to consistently throw ArgumentException when field type is incompatible with F.
|
||||
//TestSuite_Struct<AccessToolsStruct, int>(
|
||||
// field, 1337, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
TestSuite_Struct<AccessToolsStruct, int>(
|
||||
field, 1337, IncompatibleFieldType(expectedCaseToConstraint));
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Fix FieldRefAccess to consistently throw ArgumentException for struct instance fields,
|
||||
// removing the need for these separate explicit tests.
|
||||
static void TestCase_StructInstance_CanCrash<T, F>(string fieldName, F testValue, string testCaseName) where T : struct
|
||||
{
|
||||
var field = AccessTools.Field(typeof(T), fieldName);
|
||||
// Superset of problematic test cases
|
||||
var availableTestCases = Merge(
|
||||
AvailableTestCases_FieldRefAccess_Struct_ByName<T, F>(fieldName),
|
||||
AvailableTestCases_FieldRefAccess_Struct_ByFieldInfo<T, F>(field),
|
||||
AvailableTestCases_StaticFieldRefAccess_ByFieldInfo<T, F>(field));
|
||||
TestCase_CanCrash(testValue, testCaseName, field, availableTestCases);
|
||||
}
|
||||
|
||||
// TODO: Fix FieldRefAccess to consistently throw ArgumentException when field type is a value type and F is a different type,
|
||||
// removing the need for these separate explicit tests.
|
||||
static void TestCase_ClassInstance_ValueTypeField_DifferentF_CanCrash<T, F>(string fieldName, F testValue,
|
||||
string testCaseName) where T : class
|
||||
{
|
||||
var field = AccessTools.Field(typeof(T), fieldName);
|
||||
// Superset of problematic test cases
|
||||
var availableTestCases = AvailableTestCases_FieldRefAccess_Class_ByFieldInfo<T, F>(field);
|
||||
TestCase_CanCrash(testValue, testCaseName, field, availableTestCases);
|
||||
}
|
||||
|
||||
// TODO: Fix StaticFieldRefAccess to consistently throw ArgumentException when field type is incompatible with F,
|
||||
// (specifically: if field type is reference type, any value type F; if field type is value type, any different F)
|
||||
// removing the need for these separate explicit tests.
|
||||
static void TestCase_Static_IncompatibleF_CanCrash<T, F>(string fieldName, F testValue,
|
||||
string testCaseName)
|
||||
{
|
||||
var field = AccessTools.Field(typeof(T), fieldName);
|
||||
// Superset of problematic test cases
|
||||
var availableTestCases = AvailableTestCases_StaticFieldRefAccess_ByFieldInfo<T, F>(field);
|
||||
TestCase_CanCrash(testValue, testCaseName, field, availableTestCases);
|
||||
}
|
||||
|
||||
static void TestCase_CanCrash<T, F>(F testValue, string testCaseName, FieldInfo field,
|
||||
Dictionary<string, IATestCase<T, F>> availableTestCases)
|
||||
{
|
||||
var instance = CloneInstancePrototype<T>(typeof(T));
|
||||
try
|
||||
{
|
||||
var origValue = field.GetValue(instance);
|
||||
var testCase = availableTestCases[testCaseName];
|
||||
var value = testCase.Get(ref instance);
|
||||
Assert.AreNotEqual(testValue, value, "expected !Equals(testValue, value) (before set)");
|
||||
testCase.Set(ref instance, testValue);
|
||||
var currentValue = field.GetValue(instance);
|
||||
Assert.AreNotEqual(testValue, currentValue, "expected !Equals(testValue, field.GetValue(instance)) (after set)");
|
||||
Console.Error.WriteLine($"Test failed as expected: origValue={origValue}, testValue={testValue}, currentValue={currentValue}");
|
||||
}
|
||||
catch (Exception ex) when (ex is InvalidProgramException || ex is NullReferenceException || ex is AccessViolationException)
|
||||
{
|
||||
// If an assertion failure or fatal crash hasn't happened yet, any of the above exceptions could be thrown,
|
||||
// depending on the environment.
|
||||
Console.Error.WriteLine("Test is known to sometimes throw:\n" + ex);
|
||||
}
|
||||
}
|
||||
|
||||
[Test, Explicit("These tests will either fail to get/set correctly or crash the runtime due to invalid IL code causing some fatal error")]
|
||||
[TestCase(typeof(AccessToolsStruct), typeof(string), "structField1", "structField1testcrash", "FieldRefAccess<T, F>(field)(instance)")]
|
||||
[TestCase(typeof(AccessToolsStruct), typeof(string), "structField1", "structField1testcrash", "FieldRefAccess<F>(typeof(T), fieldName)(instance)")]
|
||||
[TestCase(typeof(AccessToolsStruct), typeof(string), "structField1", "structField1testcrash", "StaticFieldRefAccess<F>(field)()")]
|
||||
[TestCase(typeof(AccessToolsStruct), typeof(int), "structField2", 1234, "FieldRefAccess<T, F>(field)(instance)")]
|
||||
[TestCase(typeof(AccessToolsStruct), typeof(int), "structField2", 1234, "FieldRefAccess<F>(typeof(T), fieldName)(instance)")]
|
||||
[TestCase(typeof(AccessToolsStruct), typeof(int), "structField2", 1234, "StaticFieldRefAccess<F>(field)()")]
|
||||
public void Test_StructInstance_CanCrash(Type typeT, Type typeF, string fieldName, object testValue, string testCaseName)
|
||||
{
|
||||
TestTools.AssertIgnoreIfVSTest(); // uncomment this to actually run the test in Visual Studio
|
||||
var method = AccessTools.Method(typeof(TestFieldRefAccess), nameof(TestCase_StructInstance_CanCrash));
|
||||
_ = method.MakeGenericMethod(typeT, typeF).Invoke(this, new object[] { fieldName, testValue, testCaseName });
|
||||
}
|
||||
|
||||
[Test, Explicit("This test will either fail to get/set correctly or crash the runtime due to invalid IL code causing some fatal error")]
|
||||
[TestCase(typeof(AccessToolsClass), typeof(object), "field2", 123f, "FieldRefAccess<T, F>(field)(instance)")] // field2 is non-static float
|
||||
[TestCase(typeof(AccessToolsClass), typeof(float?), "field2", 123f, "FieldRefAccess<T, F>(field)(instance)")]
|
||||
[TestCase(typeof(AccessToolsClass), typeof(IComparable), "field2", 123f, "FieldRefAccess<T, F>(field)(instance)")]
|
||||
public void Test_ClassInstance_ValueTypeField_DifferentF_CanCrash(Type typeT, Type typeF, string fieldName, object testValue, string testCaseName)
|
||||
{
|
||||
TestTools.AssertIgnoreIfVSTest(); // uncomment this to actually run the test in Visual Studio
|
||||
var method = AccessTools.Method(typeof(TestFieldRefAccess), nameof(TestCase_ClassInstance_ValueTypeField_DifferentF_CanCrash));
|
||||
_ = method.MakeGenericMethod(typeT, typeF).Invoke(this, new object[] { fieldName, testValue, testCaseName });
|
||||
}
|
||||
|
||||
[Test, Explicit("This test will either fail to get/set correctly or crash the runtime due to invalid IL code causing some fatal error")]
|
||||
[TestCase(typeof(AccessToolsClass), typeof(int), "field4", 321, "StaticFieldRefAccess<F>(field)()")] // field4 is static string
|
||||
[TestCase(typeof(AccessToolsStruct), typeof(object), "structField3", 456, "StaticFieldRefAccess<F>(field)()")] // structField3 is static int
|
||||
[TestCase(typeof(AccessToolsStruct), typeof(int?), "structField3", 456, "StaticFieldRefAccess<F>(field)()")]
|
||||
[TestCase(typeof(AccessToolsStruct), typeof(IComparable), "structField3", 456, "StaticFieldRefAccess<F>(field)()")]
|
||||
public void Test_Static_IncompatibleF_CanCrash(Type typeT, Type typeF, string fieldName, object testValue, string testCaseName)
|
||||
{
|
||||
TestTools.AssertIgnoreIfVSTest(); // uncomment this to actually run the test in Visual Studio
|
||||
var method = AccessTools.Method(typeof(TestFieldRefAccess), nameof(TestCase_Static_IncompatibleF_CanCrash));
|
||||
_ = method.MakeGenericMethod(typeT, typeF).Invoke(this, new object[] { fieldName, testValue, testCaseName });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user