diff --git a/Harmony/Tools/AccessTools.cs b/Harmony/Tools/AccessTools.cs
index f8dd8726..a85a5d17 100644
--- a/Harmony/Tools/AccessTools.cs
+++ b/Harmony/Tools/AccessTools.cs
@@ -782,30 +782,69 @@ namespace HarmonyLib
}).ToArray();
}
- /// A read/writable reference to an instance field
- /// The class the field is defined in or "object" if type cannot be accessed at compile time
- /// The type of the field
- /// The runtime instance to access the field (leave empty for static fields)
- /// An readable/assignable object representing the field
+ /// A readable/assignable reference delegate to an instance field of a class or static field (NOT an instance field of a struct)
+ ///
+ /// An arbitrary type if the field is static; otherwise the class that defines the field, or a parent class (including ),
+ /// implemented interface, or derived class of this type
+ ///
+ ///
+ /// 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 is assignable from the field's type
+ ///
+ /// The runtime instance to access the field (ignored and can be omitted for static fields)
+ /// A readable/assignable reference to the field
+ /// Null instance passed to a non-static field ref delegate
+ ///
+ /// Instance of invalid type passed to a non-static field ref delegate
+ /// (this can happen if is a parent class or interface of the field's declaring type)
+ ///
+ ///
+ ///
+ /// 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 instead.
+ ///
+ ///
+ /// Note that is not required to be the field's declaring type. It can be a parent class (including ),
+ /// implemented interface, or a derived class of the field's declaring type ("instanceOfT is FieldDeclaringType " must be possible).
+ /// Specifically, must be assignable from OR to the field's declaring type.
+ /// Technically, this allows Nullable , although Nullable 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.
+ ///
+ ///
+ /// Similarly, is not required to be the field's field type, unless that type is a value type.
+ /// It can be a parent class (including object ) 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, must be assignable from
+ /// the field's field type; and for value types, must be exactly the field's field type.
+ ///
+ ///
+ /// This delegate supports static fields, even those defined in structs, for legacy reasons.
+ /// For such static fields, is effectively ignored.
+ /// This is also the reason that this delegate lacks a generic class constraint (it was added to certain FieldRefAccess methods,
+ /// but such a constraint cannot be added to this delegate without breaking binary compatibility).
+ /// Consider using (and StaticFieldRefAccess methods that return it) instead for static fields.
+ ///
+ ///
///
- public delegate ref F FieldRef(T obj = default);
+ public delegate ref F FieldRef(T instance = default);
- /// Creates an instance field reference
- /// The class the field is defined in
- /// The type of the field
+ /// Creates a field reference delegate for an instance field of a class
+ /// The class that defines the instance field, or derived class of this type
+ ///
+ /// 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 is assignable from the field's type
+ ///
/// The name of the field
- /// A read and writable field reference delegate
+ /// A readable/assignable delegate
///
- public static FieldRef FieldRefAccess(string fieldName)
+ public static FieldRef FieldRefAccess(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(fi);
+ return FieldRefAccessInternal(GetInstanceField(typeof(T), fieldName), needCastclass: false);
}
catch (Exception ex)
{
@@ -813,62 +852,363 @@ namespace HarmonyLib
}
}
- /// Creates an instance field reference for a specific instance
- /// The class the field is defined in
- /// The type of the field
+ /// Creates an instance field reference for a specific instance of a class
+ /// The class that defines the instance field, or derived class of this type
+ ///
+ /// 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 is assignable from the field's type
+ ///
/// The instance
/// The name of the field
- /// An readable/assignable object representing the field
+ /// A readable/assignable reference to the field
///
- public static ref F FieldRefAccess(T instance, string fieldName)
+ public static ref F FieldRefAccess(T instance, string fieldName) where T : class
{
- return ref FieldRefAccess(fieldName)(instance);
+ if (instance is null)
+ throw new ArgumentNullException(nameof(instance));
+ if (fieldName is null)
+ throw new ArgumentNullException(nameof(fieldName));
+ try
+ {
+ return ref FieldRefAccessInternal(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);
+ }
}
- /// Creates an instance field reference delegate for a private type
- /// The type of the field
- /// The class/type
+ /// Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct)
+ ///
+ /// 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 is assignable from the field's type
+ ///
+ ///
+ /// The type that defines the field, or derived class of this type; must not be a struct type unless the field is static
+ ///
/// The name of the field
- /// A read and writable delegate
+ ///
+ /// A readable/assignable delegate with T=object
+ /// (for static fields, the instance delegate parameter is ignored)
+ ///
+ ///
+ /// This method supports static fields, even those defined in structs, for legacy reasons.
+ /// Consider using (and other overloads) instead for static fields.
+ ///
///
public static FieldRef FieldRefAccess(Type type, string fieldName)
{
- return FieldRefAccess(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(fieldInfo, needCastclass: true);
+ }
+ catch (Exception ex)
+ {
+ throw new ArgumentException($"FieldRefAccess<{typeof(F)}> for {type}, {fieldName} caused an exception", ex);
+ }
}
- /// Creates an instance field reference delegate for a fieldinfo
- /// The class the field is defined in or "object" if type cannot be accessed at compile time
- /// The type of the field
- /// The field of the field
- /// A read and writable delegate
+ /// Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct)
+ ///
+ /// An arbitrary type if the field is static; otherwise the class that defines the field, or a parent class (including ),
+ /// implemented interface, or derived class of this type ("instanceOfT is FieldDeclaringType " must be possible)
+ ///
+ ///
+ /// 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 is assignable from the field's type
+ ///
+ /// The field
+ /// A readable/assignable delegate
+ ///
+ ///
+ /// This method supports static fields, even those defined in structs, for legacy reasons.
+ /// For such static fields, is effectively ignored.
+ /// Consider using (and other overloads) instead for static fields.
+ ///
+ ///
///
- public static FieldRef FieldRefAccess(FieldInfo fieldInfo)
+ public static FieldRef FieldRefAccess(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(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(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}";
+ /// Creates a field reference for an instance field of a class
+ ///
+ /// The type that defines the field; or a parent class (including ), implemented interface, or derived class of this type
+ /// ("instanceOfT is FieldDeclaringType " must be possible)
+ ///
+ ///
+ /// 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 is assignable from the field's type
+ ///
+ /// The instance
+ /// The field
+ /// A readable/assignable reference to the field
+ ///
+ public static ref F FieldRefAccess(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(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 FieldRefAccessInternal(FieldInfo fieldInfo, bool needCastclass) where T : class
+ {
+ ValidateFieldType(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)dm.Generate().CreateDelegate(typeof(FieldRef));
+ }
+
+ /// A readable/assignable reference delegate to an instance field of a struct
+ /// The struct that defines the instance field
+ ///
+ /// 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 is assignable from the field's type
+ ///
+ /// A reference to the runtime instance to access the field
+ /// A readable/assignable reference to the field
+ ///
+ public delegate ref F StructFieldRef(ref T instance) where T : struct;
+
+ /// Creates a field reference delegate for an instance field of a struct
+ /// The struct that defines the instance field
+ ///
+ /// 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 is assignable from the field's type
+ ///
+ /// The name of the field
+ /// A readable/assignable delegate
+ ///
+ public static StructFieldRef StructFieldRefAccess(string fieldName) where T : struct
+ {
+ if (fieldName is null)
+ throw new ArgumentNullException(nameof(fieldName));
+ try
+ {
+ return StructFieldRefAccessInternal(GetInstanceField(typeof(T), fieldName));
+ }
+ catch (Exception ex)
+ {
+ throw new ArgumentException($"StructFieldRefAccess<{typeof(T)}, {typeof(F)}> for {fieldName} caused an exception", ex);
+ }
+ }
+
+ /// Creates an instance field reference for a specific instance of a struct
+ /// The struct that defines the instance field
+ ///
+ /// 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 is assignable from the field's type
+ ///
+ /// The instance
+ /// The name of the field
+ /// A readable/assignable reference to the field
+ ///
+ public static ref F StructFieldRefAccess(ref T instance, string fieldName) where T : struct
+ {
+ if (fieldName is null)
+ throw new ArgumentNullException(nameof(fieldName));
+ try
+ {
+ return ref StructFieldRefAccessInternal(GetInstanceField(typeof(T), fieldName))(ref instance);
+ }
+ catch (Exception ex)
+ {
+ throw new ArgumentException($"StructFieldRefAccess<{typeof(T)}, {typeof(F)}> for {instance}, {fieldName} caused an exception", ex);
+ }
+ }
+
+ /// Creates a field reference delegate for an instance field of a struct
+ /// The struct that defines the instance field
+ ///
+ /// 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 is assignable from the field's type
+ ///
+ /// The field
+ /// A readable/assignable delegate
+ public static StructFieldRef StructFieldRefAccess(FieldInfo fieldInfo) where T : struct
+ {
+ if (fieldInfo is null)
+ throw new ArgumentNullException(nameof(fieldInfo));
+ try
+ {
+ ValidateStructField(fieldInfo);
+ return StructFieldRefAccessInternal(fieldInfo);
+ }
+ catch (Exception ex)
+ {
+ throw new ArgumentException($"StructFieldRefAccess<{typeof(T)}, {typeof(F)}> for {fieldInfo} caused an exception", ex);
+ }
+ }
+
+ /// Creates a field reference for an instance field of a struct
+ /// The struct that defines the instance field
+ ///
+ /// 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 is assignable from the field's type
+ ///
+ /// The instance
+ /// The field
+ /// A readable/assignable reference to the field
+ ///
+ public static ref F StructFieldRefAccess(ref T instance, FieldInfo fieldInfo) where T : struct
+ {
+ if (fieldInfo is null)
+ throw new ArgumentNullException(nameof(fieldInfo));
+ try
+ {
+ ValidateStructField(fieldInfo);
+ return ref StructFieldRefAccessInternal(fieldInfo)(ref instance);
+ }
+ catch (Exception ex)
+ {
+ throw new ArgumentException($"StructFieldRefAccess<{typeof(T)}, {typeof(F)}> for {instance}, {fieldInfo} caused an exception", ex);
+ }
+ }
+
+ static void ValidateStructField(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 StructFieldRefAccessInternal(FieldInfo fieldInfo) where T : struct
+ {
+ ValidateFieldType(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)dm.Generate().CreateDelegate(typeof(FieldRef));
+ return (StructFieldRef)dm.Generate().CreateDelegate(typeof(StructFieldRef));
}
+ 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;
+ }
+
+ /// A readable/assignable reference delegate to a static field
+ ///
+ /// 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 is assignable from the field's type
+ ///
+ /// A readable/assignable reference to the field
+ ///
+ public delegate ref F FieldRef();
+
/// Creates a static field reference
- /// The class the field is defined in or "object" if type cannot be accessed at compile time
- /// The type of the field
+ /// The type (can be class or struct) the field is defined in
+ ///
+ /// 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 is assignable from the field's type
+ ///
/// The name of the field
- /// An readable/assignable object representing the static field
+ /// A readable/assignable reference to the field
///
public static ref F StaticFieldRefAccess(string fieldName)
{
@@ -876,67 +1216,86 @@ namespace HarmonyLib
}
/// Creates a static field reference
- /// The type of the field
- /// The class/type
+ ///
+ /// 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 is assignable from the field's type
+ ///
+ /// The type (can be class or struct) the field is defined in
/// The name of the field
- /// An readable/assignable object representing the static field
+ /// A readable/assignable reference to the field
///
public static ref F StaticFieldRefAccess(Type type, string fieldName)
{
- const BindingFlags bf = BindingFlags.NonPublic |
- BindingFlags.Static |
- BindingFlags.DeclaredOnly;
try
{
- var fi = type.GetField(fieldName, bf);
- return ref StaticFieldRefAccess(fi)();
+ var fieldInfo = Field(type, fieldName);
+ if (fieldInfo is null)
+ throw new MissingFieldException(type.Name, fieldName);
+ return ref StaticFieldRefAccessInternal(fieldInfo)();
}
catch (Exception ex)
{
throw new ArgumentException($"StaticFieldRefAccess<{typeof(F)}> for {type}, {fieldName} caused an exception", ex);
- throw;
}
}
/// Creates a static field reference
- /// The class the field is defined in or "object" if type cannot be accessed at compile time
- /// The type of the field
+ /// An arbitrary type (by convention, the type the field is defined in)
+ ///
+ /// 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 is assignable from the field's type
+ ///
/// The field
- /// An readable/assignable object representing the static field
+ /// A readable/assignable reference to the field
+ ///
+ /// The type parameter is only used in exception messaging and to distinguish between this method overload
+ /// and the overload (which returns a rather than a reference).
+ ///
///
public static ref F StaticFieldRefAccess(FieldInfo fieldInfo)
{
+ if (fieldInfo is null)
+ throw new ArgumentNullException(nameof(fieldInfo));
try
{
- return ref StaticFieldRefAccess(fieldInfo)();
+ return ref StaticFieldRefAccessInternal(fieldInfo)();
}
catch (Exception ex)
{
throw new ArgumentException($"StaticFieldRefAccess<{typeof(T)}, {typeof(F)}> for {fieldInfo} caused an exception", ex);
- throw;
}
}
- /// A read/writable reference delegate to a static field
- /// The type of the field
- /// An readable/assignable object representing the static field
- ///
- public delegate ref F FieldRef();
-
/// Creates a static field reference delegate
- /// The type of the field
+ ///
+ /// 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 is assignable from the field's type
+ ///
/// The field
- /// A read and writable delegate
+ /// A readable/assignable delegate
///
public static FieldRef StaticFieldRefAccess(FieldInfo fieldInfo)
{
- if (fieldInfo == null)
+ if (fieldInfo is null)
throw new ArgumentNullException(nameof(fieldInfo));
- var type = fieldInfo.DeclaringType;
+ try
+ {
+ return StaticFieldRefAccessInternal(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 StaticFieldRefAccessInternal(FieldInfo fieldInfo)
+ {
+ if (fieldInfo.IsStatic is false)
+ throw new ArgumentException("Field must be static");
+ ValidateFieldType(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)dm.Generate().CreateDelegate(typeof(FieldRef));
}
+ static void ValidateFieldType(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");
+ }
+ }
+
/// Creates a delegate to a given method
/// The delegate Type
/// The method to create a delegate from.
diff --git a/HarmonyTests/Tools/TestFieldRefAccess.cs b/HarmonyTests/Tools/TestFieldRefAccess.cs
index 0a9273d5..46cdb7e2 100644
--- a/HarmonyTests/Tools/TestFieldRefAccess.cs
+++ b/HarmonyTests/Tools/TestFieldRefAccess.cs
@@ -57,31 +57,30 @@ namespace HarmonyLibTests
}
}
- // TODO: AccessTools.StructFieldRefAccess
- //static IATestCase ATestCase(AccessTools.StructFieldRef fieldRef) where T : struct
- //{
- // return new StructFieldRefTestCase(fieldRef);
- //}
+ static IATestCase ATestCase(AccessTools.StructFieldRef fieldRef) where T : struct
+ {
+ return new StructFieldRefTestCase(fieldRef);
+ }
- //class StructFieldRefTestCase : IATestCase where T : struct
- //{
- // readonly AccessTools.StructFieldRef fieldRef;
+ class StructFieldRefTestCase : IATestCase where T : struct
+ {
+ readonly AccessTools.StructFieldRef fieldRef;
- // public StructFieldRefTestCase(AccessTools.StructFieldRef fieldRef)
- // {
- // this.fieldRef = fieldRef;
- // }
+ public StructFieldRefTestCase(AccessTools.StructFieldRef 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 ATestCase(AccessTools.FieldRef 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(field)(instance)"] = ATestCase(instance => ref AccessTools.FieldRefAccess(field)(instance)),
["FieldRefAccess(field)()"] = ATestCase(instance => ref AccessTools.FieldRefAccess(field)()),
- //["FieldRefAccess(instance, field)"] = ATestCase(instance => ref AccessTools.FieldRefAccess(instance, field)), // TODO: implement this overload
+ ["FieldRefAccess(instance, field)"] = ATestCase(instance => ref AccessTools.FieldRefAccess(instance, field)),
};
}
- // TODO: Once generic class constraint is added to most FieldRefAccess methods, remove the calls that are no longer compilable.
static Dictionary> AvailableTestCases_FieldRefAccess_Struct_ByName(
string fieldName) where T : struct
{
return new Dictionary>
{
- ["FieldRefAccess(fieldName)(instance)"] = ATestCase(instance => ref AccessTools.FieldRefAccess(fieldName)(instance)),
- ["FieldRefAccess(instance, fieldName)"] = ATestCase(instance => ref AccessTools.FieldRefAccess(instance, fieldName)),
["FieldRefAccess(typeof(T), fieldName)(instance)"] = ATestCase(instance => ref AccessTools.FieldRefAccess(typeof(T), fieldName)(instance)),
["FieldRefAccess(typeof(T), fieldName)()"] = ATestCase(instance => ref AccessTools.FieldRefAccess(typeof(T), fieldName)()),
};
}
- // TODO: Once generic class constraint is added to most FieldRefAccess methods, remove the calls that are no longer compilable.
- static Dictionary> AvailableTestCases_FieldRefAccess_Struct_ByFieldInfo(
- FieldInfo field) where T : struct
- {
- return new Dictionary>
- {
- ["FieldRefAccess(field)(instance)"] = ATestCase(instance => ref AccessTools.FieldRefAccess(field)(instance)),
- ["FieldRefAccess(field)()"] = ATestCase(instance => ref AccessTools.FieldRefAccess(field)()),
- };
- }
-
- // TODO: StructFieldRefAccess
static Dictionary> AvailableTestCases_StructFieldRefAccess(FieldInfo field,
string fieldName) where T : struct
{
return new Dictionary>
{
- //["StructFieldRefAccess(fieldName)(ref instance)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess(fieldName)(ref instance)),
- //["StructFieldRefAccess(ref instance, fieldName)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess(ref instance, fieldName)),
- //["StructFieldRefAccess(field)(ref instance)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess(field)(ref instance)),
- //["StructFieldRefAccess(ref instance, field)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess(ref instance, field)),
+ ["StructFieldRefAccess(fieldName)(ref instance)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess(fieldName)(ref instance)),
+ ["StructFieldRefAccess(ref instance, fieldName)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess(ref instance, fieldName)),
+ ["StructFieldRefAccess(field)(ref instance)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess(field)(ref instance)),
+ ["StructFieldRefAccess(ref instance, field)"] = ATestCase((ref T instance) => ref AccessTools.StructFieldRefAccess(ref instance, field)),
};
}
@@ -383,7 +359,6 @@ namespace HarmonyLibTests
var availableTestCases = Merge(
AvailableTestCases_StructFieldRefAccess(field, field.Name),
AvailableTestCases_FieldRefAccess_Struct_ByName(field.Name),
- AvailableTestCases_FieldRefAccess_Struct_ByFieldInfo(field),
AvailableTestCases_StaticFieldRefAccess_ByName(field.Name),
AvailableTestCases_StaticFieldRefAccess_ByFieldInfo(field));
new ATestSuite(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 PublicField(Dictionary expectedCaseToConstraint)
- {
- return expectedCaseToConstraint.Merge(ReusableConstraints(new Dictionary
- {
- ["FieldRefAccess(fieldName)(instance)"] = Throws.InstanceOf(),
- ["FieldRefAccess(instance, fieldName)"] = Throws.InstanceOf(),
- ["StaticFieldRefAccess(fieldName)"] = Throws.InstanceOf(),
- ["StaticFieldRefAccess(typeof(T), fieldName)"] = Throws.InstanceOf(),
- }).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 InterfaceT(Dictionary expectedCaseToConstraint)
- {
- return expectedCaseToConstraint.Merge(ReusableConstraints(new Dictionary
- {
- ["FieldRefAccess(field)(instance)"] = Throws.Exception,
- ["FieldRefAccess(field)()"] = Throws.Exception,
- }).Where(pair => expectedCaseToConstraint.ContainsKey(pair.Key)));
- }
-
static Dictionary FieldMissingOnTypeT(Dictionary expectedCaseToConstraint)
{
return expectedCaseToConstraint.Merge(ReusableConstraints(new Dictionary
{
- // TODO: StructFieldRefAccess
- //["StructFieldRefAccess(fieldName)(ref instance)"] = Throws.InstanceOf(),
- //["StructFieldRefAccess(ref instance, fieldName)"] = Throws.InstanceOf(),
+ ["StructFieldRefAccess(fieldName)(ref instance)"] = Throws.InstanceOf(),
+ ["StructFieldRefAccess(ref instance, fieldName)"] = Throws.InstanceOf(),
["FieldRefAccess(fieldName)(instance)"] = Throws.InstanceOf(),
["FieldRefAccess(instance, fieldName)"] = Throws.InstanceOf(),
["FieldRefAccess(typeof(T), fieldName)(instance)"] = Throws.InstanceOf(),
@@ -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
+ var newExpectedCaseToConstraint = FieldMissingOnTypeT(expectedCaseToConstraint).Merge(ReusableConstraints(new Dictionary
{
- // TODO: StructFieldRefAccess
- //["StructFieldRefAccess(field)(ref instance)"] = Throws.InstanceOf(),
- //["StructFieldRefAccess(ref instance, field)"] = Throws.InstanceOf(),
- ["FieldRefAccess(field)(instance)"] = SkipTest("incompatible instance type can cause crash"), // TODO: should be InvalidCastException if not already another Throws constraint
- //["FieldRefAccess(instance, field)"] = Throws.InstanceOf(), // TODO: implement this overload
+ ["StructFieldRefAccess(field)(ref instance)"] = Throws.InstanceOf(),
+ ["StructFieldRefAccess(ref instance, field)"] = Throws.InstanceOf(),
+ ["FieldRefAccess(instance, field)"] = Throws.InstanceOf(),
}).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
+ {
+ ["FieldRefAccess(field)(instance)"] = Throws.TypeOf(),
+ }).Where(pair => expectedCaseToConstraint.TryGetValue(pair.Key, out var constraint) && constraint.Resolve() is ThrowsNothingConstraint));
}
static Dictionary IncompatibleTypeT(Dictionary 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
{
- ["FieldRefAccess(field)(instance)"] = Throws.Exception, // TODO: should be ArgumentException
- ["FieldRefAccess(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 StaticIncompatibleTypeT(Dictionary expectedCaseToConstraint)
- {
- return expectedCaseToConstraint.Merge(ReusableConstraints(new Dictionary
- {
- ["FieldRefAccess(field)(instance)"] = Throws.Exception,
- ["FieldRefAccess(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 FieldNotInheritedBySubClass(Dictionary expectedCaseToConstraint)
- {
- return expectedCaseToConstraint.Merge(ReusableConstraints(new Dictionary
- {
- // Following search for only declared fields (excludes all fields from parents).
- ["FieldRefAccess(fieldName)(instance)"] = Throws.InstanceOf(),
- ["FieldRefAccess(instance, fieldName)"] = Throws.InstanceOf(),
- ["StaticFieldRefAccess(fieldName)"] = Throws.InstanceOf(),
- ["StaticFieldRefAccess(typeof(T), fieldName)"] = Throws.InstanceOf(),
- }).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 FieldInheritedBySubClass(Dictionary expectedCaseToConstraint)
- {
- return expectedCaseToConstraint.Merge(ReusableConstraints(new Dictionary
- {
- // 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(fieldName)(instance)"] = Throws.InstanceOf(),
- ["FieldRefAccess(instance, fieldName)"] = Throws.InstanceOf(),
+ ["FieldRefAccess(field)(instance)"] = Throws.InstanceOf(),
+ ["FieldRefAccess(field)()"] = Throws.InstanceOf(),
}).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());
}
return newExpectedCaseToConstraint;
}
@@ -535,11 +452,11 @@ namespace HarmonyLibTests
["FieldRefAccess(typeof(T), fieldName)()"] = Throws.TypeOf(),
["FieldRefAccess(field)(instance)"] = Throws.Nothing,
["FieldRefAccess(field)()"] = Throws.TypeOf(),
- //["FieldRefAccess(instance, field)"] = Throws.Nothing, // TODO: implement this overload
+ ["FieldRefAccess(instance, field)"] = Throws.Nothing,
["StaticFieldRefAccess(fieldName)"] = Throws.InstanceOf(),
["StaticFieldRefAccess(typeof(T), fieldName)"] = Throws.InstanceOf(),
- ["StaticFieldRefAccess(field)()"] = Throws.Exception, // TODO: should be ArgumentException
- ["StaticFieldRefAccess(field)"] = Throws.Exception, // TODO: should be ArgumentException
+ ["StaticFieldRefAccess(field)()"] = Throws.InstanceOf(),
+ ["StaticFieldRefAccess(field)"] = Throws.InstanceOf(),
});
static readonly Dictionary expectedCaseToConstraint_ClassStatic =
@@ -551,7 +468,7 @@ namespace HarmonyLibTests
["FieldRefAccess(typeof(T), fieldName)()"] = Throws.Nothing,
["FieldRefAccess(field)(instance)"] = Throws.Nothing, // T is ignored
["FieldRefAccess(field)()"] = Throws.Nothing, // T is ignored
- //["FieldRefAccess(instance, field)"] = Throws.InstanceOf