{"record":{"id":"c484c1f322b143fe","repo":"pardeike/Harmony","slug":"state-type-mismatch-in-patch-fix-declaringtype-fullname-fix","errorCode":null,"errorMessage":"__state type mismatch in patch \"{fix.DeclaringType.FullName}.{fix.Name}\": previous __state was declared as \"{maybeLocal.LocalType.FullName}\" but this patch expects \"{type.FullName}\"","messagePattern":"__state type mismatch in patch \"(.+?)\\.(.+?)\": previous __state was declared as \"(.+?)\" but this patch expects \"(.+?)\"","errorType":"exception","errorClass":"HarmonyException","httpStatus":null,"severity":"error","filePath":"Harmony/Internal/MethodCreator.cs","lineNumber":88,"sourceCode":"\r\n\t\t\tconfig.WithFixes(fix =>\r\n\t\t\t{\r\n\t\t\t\tvar declaringType = fix.DeclaringType;\r\n\t\t\t\tif (declaringType is null)\r\n\t\t\t\t\treturn;\r\n\t\t\t\tvar varName = declaringType.AssemblyQualifiedName;\r\n\t\t\t\t_ = config.localVariables.TryGetValue(varName, out var maybeLocal);\r\n\t\t\t\tforeach (var injection in config.InjectionsFor(fix, InjectionType.State))\r\n\t\t\t\t{\r\n\t\t\t\t\tvar parameterType = injection.parameterInfo.ParameterType;\r\n\t\t\t\t\tvar type = parameterType.IsByRef ? parameterType.GetElementType() : parameterType;\r\n\t\t\t\t\tif (maybeLocal != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (!type.IsAssignableFrom(maybeLocal.LocalType))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar message = $\"__state type mismatch in patch \\\"{fix.DeclaringType.FullName}.{fix.Name}\\\": \" +\r\n\t\t\t\t\t\t\t$\"previous __state was declared as \\\"{maybeLocal.LocalType.FullName}\\\" but this patch expects \\\"{type.FullName}\\\"\";\r\n\t\t\t\t\t\t\tthrow new HarmonyException(message);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tvar privateStateVariable = config.DeclareLocal(type);\r\n\t\t\t\t\tconfig.AddLocal(varName, privateStateVariable);\r\n\t\t\t\t\tconfig.AddCodes(this.GenerateVariableInit(privateStateVariable));\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\tconfig.finalizedVariable = null;\r\n\t\t\tif (config.finalizers.Count > 0)\r\n\t\t\t{\r\n\t\t\t\tconfig.finalizedVariable = config.DeclareLocal(typeof(bool));\r\n\t\t\t\tconfig.AddCodes(this.GenerateVariableInit(config.finalizedVariable));\r\n\t\t\t\tconfig.exceptionVariable = config.DeclareLocal(typeof(Exception));\r","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/pardeike/Harmony/blob/e7872dc17008bc0ca2b3de1f53fd4120dbd7a17c/Harmony/Internal/MethodCreator.cs#L70-L106","documentation":"When multiple patches share per-patch __state, Harmony declares one local per patch class; if a later patch (fix) reuses the same declaring type key and expects a different __state type than the already-declared local, Harmony throws HarmonyException naming both types and the offending patch. The declared local type must be assignable-from the previously declared one (checked via Type.IsAssignableFrom).","triggerScenarios":"Two patch methods in the same patch class declaring different __state parameter types (e.g. one takes `int __state`, another `long __state`); adding a second patch method to an existing patch class after a code change with an incompatible __state type; base/derived class patch state collisions via AssignLocal state sharing.","commonSituations":"Refactoring a patch class and adding new patch methods with a changed __state type; copying a patch method into an existing patch class without adjusting __state to match; generic patch classes instantiated with different type parameters.","solutions":["Make all __state parameters within one patch class use the exact same (assignable) type","Store the old state in a differently-named state type or move the new patch method to its own patch class","Use a shared object-typed __state and cast internally, if a unified type is impractical","Read the exception: it names the patch, the existing type, and the expected type — align them"],"exampleFix":"// before\nclass Patch { void Prefix(int __state) {} void Postfix(string __state) {} } // mismatch\n// after\nclass Patch { void Prefix(int __state) {} void Postfix(int __state) {} }\n// or move Postfix to a separate class","handlingStrategy":"validation","validationCode":"// before patching, assert all __state params in a patch class agree:\nvar types = typeof(MyPatches).GetMethods()\n    .SelectMany(m => m.GetParameters())\n    .Where(p => p.Name == \"__state\")\n    .Select(p => p.ParameterType.IsByRef ? p.ParameterType.GetElementType() : p.ParameterType)\n    .Distinct().ToList();\nif (types.Count > 1) throw new InvalidOperationException($\"__state types disagree: {string.Join(\", \", types)}\");","typeGuard":"bool StateTypesCompatible(Type declared, Type wanted) => declared.IsAssignableFrom(wanted) || wanted.IsAssignableFrom(declared);","tryCatchPattern":"try { harmony.PatchAll(typeof(MyPatches)); }\ncatch (HarmonyException ex) when (ex.Message.Contains(\"__state type mismatch\"))\n{ Log.Error($\"Fix __state declarations: {ex.Message}\"); throw; }","preventionTips":["Keep exactly one __state type per patch class; document it at the top of the class","Move patches needing different state into separate patch classes","Review __state parameters whenever adding a patch method to an existing class","Prefer object-typed shared state with explicit casting if flexibility is needed"],"tags":["harmony","patching","type-mismatch","state"],"backgroundTag":"type-mismatch","analyzedSha":"e7872dc17008bc0ca2b3de1f53fd4120dbd7a17c","analyzedAt":"2026-09-15T22:47:11.550Z","contentChangedAt":"2026-09-15T22:47:11.550Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}