pardeike/Harmony · error · ArgumentException

FieldDeclaringType must be assignable from or to T…

Error message

FieldDeclaringType must be assignable from or to T (FieldRefAccess instance type) - "instanceOfT is FieldDeclaringType" must be possible

What it means

FieldRefNeedsClasscast validates that the generic instance type T used with FieldRefAccess can be cast to the field's declaring type (or vice versa). If neither type is assignable to the other, a field ref cannot be formed, so Harmony throws rather than emit invalid castclass IL.

Solutions

  1. Pass T as the field's actual declaring type (or a type in its inheritance chain)
  2. Resolve the fieldInfo with AccessTools.Field(typeof(DeclaringType), "name") so declaringType matches T
  3. Check fieldInfo.DeclaringType and your T for mutual assignability before calling

Example fix

// before
var acc = Tools.FieldRefAccess<Base, int>(AccessTools.Field(typeof(Unrelated), "hp"));
// after
var acc = Tools.FieldRefAccess<Base, int>(AccessTools.Field(typeof(Base), "hp"));
Defensive patterns

Strategy: validation

Validate before calling

var fi = AccessTools.Field(typeof(Declaring), name);
if (fi == null || !(typeof(T).IsAssignableFrom(fi.DeclaringType) || fi.DeclaringType.IsAssignableFrom(typeof(T))))
    throw new ArgumentException("T and field declaring type are not assignable");

Type guard

bool FieldRefCompatible<T>(FieldInfo fi) =>
    fi != null && (typeof(T).IsAssignableFrom(fi.DeclaringType) || fi.DeclaringType.IsAssignableFrom(typeof(T)));

Try / catch

try { var acc = Tools.FieldRefAccess<T, F>(fi); }
catch (ArgumentException ex) when (ex.Message.StartsWith("FieldDeclaringType must be assignable")) { /* use declaring type as T */ }

Prevention

When it happens

Trigger: Calling FieldRefAccess<T, F>(fieldInfo) where T (delegateInstanceType) and the field's declaringType are unrelated types (neither IsAssignableFrom the other).

Common situations: Using a derived wrapper class as T while the field is declared on a base class the wrapper doesn't inherit; mixing structs/classes; target library refactor moved the field to an unrelated type.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of pardeike/Harmony@e7872dc170 (2026-09-15). Data as JSON: /api/errors/ffd9a40a4e53c0ef. Report an issue: GitHub.

Appendix: source

Thrown at Harmony/Tools/Tools.cs:134

		internal 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;
		}

		internal 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;
		}

		internal 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)");
		}
	}
}

View on GitHub (pinned to e7872dc170)