diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs index 2d69d585..0af170ad 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs @@ -1,17 +1,8 @@ #nullable enable using IPA.Config.Data; -using IPA.Config.Stores.Attributes; -using IPA.Logging; using System; -using System.Collections.Generic; -using System.ComponentModel; using System.Linq; -using System.Linq.Expressions; -using System.Reflection; using System.Reflection.Emit; -using System.Text; -using System.Threading; -using System.Threading.Tasks; #if NET3 using Net3_Proxy; using Array = Net3_Proxy.Array; @@ -46,12 +37,27 @@ namespace IPA.Config.Stores var endLabel = il.DefineLabel(); - // TODO: when we have a nullable, we need to save to a local to call methods if (member.IsNullable) { - il.Emit(OpCodes.Dup); + using var valueTypeLocal = GetLocal.Allocate(member.Type); + var passedNull = il.DefineLabel(); + + il.Emit(OpCodes.Stloc, valueTypeLocal); + il.Emit(OpCodes.Ldloca, valueTypeLocal); il.Emit(OpCodes.Call, member.Nullable_HasValue.GetGetMethod()); - il.Emit(OpCodes.Brfalse, endLabel); + il.Emit(OpCodes.Brtrue, passedNull); + + if (member.ConversionType.IsValueType) + { + il.Emit(OpCodes.Ldloca, valueTypeLocal); + il.Emit(OpCodes.Initobj, member.Type); + } + il.Emit(OpCodes.Ldloc, valueTypeLocal); + il.Emit(OpCodes.Br, endLabel); + + il.MarkLabel(passedNull); + + il.Emit(OpCodes.Ldloca, valueTypeLocal); il.Emit(OpCodes.Call, member.Nullable_Value.GetGetMethod()); } diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs index 07904fce..18744fed 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs @@ -41,7 +41,7 @@ namespace IPA.Config.Stores il.Emit(OpCodes.Callvirt, IGeneratedStore_Deserialize); } - private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, LocalAllocator GetLocal, + private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, LocalAllocator GetLocal, Action thisarg, Action parentobj) { if (!member.IsNullable) throw new InvalidOperationException("EmitDeserializeNullable called for non-nullable!"); @@ -53,7 +53,7 @@ namespace IPA.Config.Stores // top of stack is the Value to deserialize; the type will be as returned from GetExpectedValueTypeForType // after, top of stack will be thing to write to field - private static void EmitDeserializeValue(ILGenerator il, SerializedMemberInfo member, Type targetType, Type expected, LocalAllocator GetLocal, + private static void EmitDeserializeValue(ILGenerator il, SerializedMemberInfo member, Type targetType, Type expected, LocalAllocator GetLocal, Action thisarg, Action parentobj) { if (typeof(Value).IsAssignableFrom(targetType)) return; // do nothing @@ -96,14 +96,14 @@ namespace IPA.Config.Stores { using var mapLocal = GetLocal.Allocate(typeof(Map)); - using var resultLocal = GetLocal.Allocate(targetType); + using var resultLocal = GetLocal.Allocate(member.IsNullable ? member.Type : targetType); using var valueLocal = GetLocal.Allocate(typeof(Value)); var structure = ReadObjectMembers(targetType); if (!structure.Any()) { - Logger.Config.Warn($"Custom value type {targetType.FullName} (when compiling serialization of" + - $" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members"); + Logger.Config.Warn($"Custom value type {targetType.FullName} (when compiling deserialization of" + + $" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members, might need a converter"); il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ldloca, resultLocal); il.Emit(OpCodes.Initobj, targetType); @@ -112,7 +112,6 @@ namespace IPA.Config.Stores { il.Emit(OpCodes.Stloc, mapLocal); - // TODO: handle creating a nullable, when applicable EmitLoad(il, member, thisarg); il.Emit(OpCodes.Stloc, resultLocal); @@ -130,7 +129,7 @@ namespace IPA.Config.Stores } } - private static void EmitDeserializeStructure(ILGenerator il, IEnumerable structure, + private static void EmitDeserializeStructure(ILGenerator il, IEnumerable structure, LocalBuilder mapLocal, LocalBuilder valueLocal, LocalAllocator GetLocal, Action thisobj, Action parentobj) { @@ -204,7 +203,7 @@ namespace IPA.Config.Stores } // emit takes the value being deserialized, logs on error, leaves nothing on stack - private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action getValue, LocalAllocator GetLocal, + private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action getValue, LocalAllocator GetLocal, Action thisobj, Action parentobj) { var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType)); diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs index 54595d76..b5a9040b 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs @@ -78,7 +78,7 @@ namespace IPA.Config.Stores if (converterAttr.UseDefaultConverterForType) converterAttr = new UseConverterAttribute(Converter.GetDefaultConverterType(member.Type)); if (converterAttr.UseDefaultConverterForType) - throw new InvalidOperationException("How did we get here?"); + throw new InvalidOperationException("How did we get here?"); member.Converter = converterAttr.ConverterType; member.IsGenericConverter = converterAttr.IsGenericConverter; @@ -175,7 +175,7 @@ namespace IPA.Config.Stores // only look at public/protected fields foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { - if (field.IsPrivate) + if (field.IsPrivate || field.IsAssembly) continue; var smi = new SerializedMemberInfo diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs index d25f8c35..63703cf8 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs @@ -28,47 +28,58 @@ namespace IPA.Config.Stores ? GetLocal.Allocate(member.Type) : default; - if (member.IsNullable) - { - il.Emit(OpCodes.Stloc, valueTypeLocal.Local); - il.Emit(OpCodes.Ldloca, valueTypeLocal.Local); - } - var endSerialize = il.DefineLabel(); if (member.AllowNull) { var passedNull = il.DefineLabel(); - il.Emit(OpCodes.Dup); if (member.IsNullable) + { + il.Emit(OpCodes.Stloc, valueTypeLocal.Local); + il.Emit(OpCodes.Ldloca, valueTypeLocal.Local); il.Emit(OpCodes.Call, member.Nullable_HasValue.GetGetMethod()); + } il.Emit(OpCodes.Brtrue, passedNull); - il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Br, endSerialize); il.MarkLabel(passedNull); - } - if (member is { IsNullable: true, HasConverter: false }) - il.Emit(OpCodes.Call, member.Nullable_Value.GetGetMethod()); + if (member.IsNullable) + { + if (!member.HasConverter) + { + il.Emit(OpCodes.Ldloca, valueTypeLocal.Local); + il.Emit(OpCodes.Call, member.Nullable_Value.GetGetMethod()); + } + } + else + { + EmitLoad(il, member, thisarg); + } + } var memberConversionType = member.ConversionType; var targetType = GetExpectedValueTypeForType(memberConversionType); if (member.HasConverter) { - using var stlocal = GetLocal.Allocate(member.IsNullable ? member.Type : memberConversionType); - using var valLocal = GetLocal.Allocate(typeof(Value)); - if (member.IsNullable) - il.Emit(OpCodes.Ldloc_S, valueTypeLocal.Local); + { + il.BeginExceptionBlock(); + il.Emit(OpCodes.Ldsfld, member.ConverterField); + il.Emit(OpCodes.Ldloc, valueTypeLocal.Local); + } + else + { + using var stlocal = GetLocal.Allocate(memberConversionType); - il.Emit(OpCodes.Stloc, stlocal); - il.BeginExceptionBlock(); - il.Emit(OpCodes.Ldsfld, member.ConverterField); - il.Emit(OpCodes.Ldloc, stlocal); + il.Emit(OpCodes.Stloc, stlocal); + il.BeginExceptionBlock(); + il.Emit(OpCodes.Ldsfld, member.ConverterField); + il.Emit(OpCodes.Ldloc, stlocal); + } if (member.IsGenericConverter) { @@ -90,18 +101,18 @@ namespace IPA.Config.Stores il.Emit(OpCodes.Call, toValue); } - il.Emit(OpCodes.Stloc, valLocal); + il.Emit(OpCodes.Stloc_1); il.BeginCatchBlock(typeof(Exception)); EmitWarnException(il, "Error serializing member using converter"); il.Emit(OpCodes.Ldnull); - il.Emit(OpCodes.Stloc, valLocal); + il.Emit(OpCodes.Stloc_1); il.EndExceptionBlock(); - il.Emit(OpCodes.Ldloc, valLocal); + il.Emit(OpCodes.Ldloc_1); } else if (targetType == typeof(Text)) { // only happens when arg is a string or char var TextCreate = typeof(Value).GetMethod(nameof(Value.Text)); - if (member.Type == typeof(char)) + if (memberConversionType == typeof(char)) { var strFromChar = typeof(char).GetMethod(nameof(char.ToString), new[] { typeof(char) }); il.Emit(OpCodes.Call, strFromChar); @@ -172,7 +183,7 @@ namespace IPA.Config.Stores if (!structure.Any()) { Logger.Config.Warn($"Custom value type {memberConversionType.FullName} (when compiling serialization of" + - $" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members"); + $" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members, might need a converter"); il.Emit(OpCodes.Pop); } else