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:
lbmaian
2020-07-19 01:54:30 -07:00
parent 52481530fc
commit 9a3da4be4e
2 changed files with 598 additions and 434 deletions
+447 -72
View File
@@ -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>
+151 -362
View File
@@ -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 });
}
}
}