You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

211 lines
9.3 KiB

  1. #nullable enable
  2. using IPA.Config.Data;
  3. using IPA.Logging;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Reflection;
  8. using System.Reflection.Emit;
  9. using System.Text;
  10. using System.Threading.Tasks;
  11. using Boolean = IPA.Config.Data.Boolean;
  12. #if NET3
  13. using Net3_Proxy;
  14. using Array = Net3_Proxy.Array;
  15. #endif
  16. namespace IPA.Config.Stores
  17. {
  18. internal static partial class GeneratedStoreImpl
  19. {
  20. // emit takes no args, leaves Value at top of stack
  21. private static void EmitSerializeMember(ILGenerator il, SerializedMemberInfo member, LocalAllocator GetLocal, Action<ILGenerator> thisarg, Action<ILGenerator> parentobj)
  22. {
  23. EmitLoad(il, member, thisarg);
  24. using var valueTypeLocal =
  25. member.IsNullable
  26. ? GetLocal.Allocate(member.Type)
  27. : default;
  28. if (member.IsNullable)
  29. {
  30. il.Emit(OpCodes.Stloc, valueTypeLocal.Local);
  31. il.Emit(OpCodes.Ldloca, valueTypeLocal.Local);
  32. }
  33. var endSerialize = il.DefineLabel();
  34. if (member.AllowNull)
  35. {
  36. var passedNull = il.DefineLabel();
  37. il.Emit(OpCodes.Dup);
  38. if (member.IsNullable)
  39. il.Emit(OpCodes.Call, member.Nullable_HasValue.GetGetMethod());
  40. il.Emit(OpCodes.Brtrue, passedNull);
  41. il.Emit(OpCodes.Pop);
  42. il.Emit(OpCodes.Ldnull);
  43. il.Emit(OpCodes.Br, endSerialize);
  44. il.MarkLabel(passedNull);
  45. }
  46. if (member.IsNullable)
  47. il.Emit(OpCodes.Call, member.Nullable_Value.GetGetMethod());
  48. var memberConversionType = member.ConversionType;
  49. var targetType = GetExpectedValueTypeForType(memberConversionType);
  50. if (member.HasConverter)
  51. {
  52. using var stlocal = GetLocal.Allocate(memberConversionType);
  53. using var valLocal = GetLocal.Allocate(typeof(Value));
  54. il.Emit(OpCodes.Stloc, stlocal);
  55. il.BeginExceptionBlock();
  56. il.Emit(OpCodes.Ldsfld, member.ConverterField);
  57. il.Emit(OpCodes.Ldloc, stlocal);
  58. if (member.IsGenericConverter)
  59. {
  60. var toValueBase = member.ConverterBase.GetMethod(nameof(ValueConverter<int>.ToValue),
  61. new[] { member.ConverterTarget, typeof(object) });
  62. var toValue = member.Converter.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
  63. .FirstOrDefault(m => m.GetBaseDefinition() == toValueBase) ?? toValueBase;
  64. il.Emit(OpCodes.Ldarg_0);
  65. il.Emit(OpCodes.Call, toValue);
  66. }
  67. else
  68. {
  69. var toValueBase = typeof(IValueConverter).GetMethod(nameof(IValueConverter.ToValue),
  70. new[] { typeof(object), typeof(object) });
  71. var toValue = member.Converter.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
  72. .FirstOrDefault(m => m.GetBaseDefinition() == toValueBase) ?? toValueBase;
  73. il.Emit(OpCodes.Box);
  74. il.Emit(OpCodes.Ldarg_0);
  75. il.Emit(OpCodes.Call, toValue);
  76. }
  77. il.Emit(OpCodes.Stloc, valLocal);
  78. il.BeginCatchBlock(typeof(Exception));
  79. EmitWarnException(il, "Error serializing member using converter");
  80. il.Emit(OpCodes.Ldnull);
  81. il.Emit(OpCodes.Stloc, valLocal);
  82. il.EndExceptionBlock();
  83. il.Emit(OpCodes.Ldloc, valLocal);
  84. }
  85. else if (targetType == typeof(Text))
  86. { // only happens when arg is a string or char
  87. var TextCreate = typeof(Value).GetMethod(nameof(Value.Text));
  88. if (member.Type == typeof(char))
  89. {
  90. var strFromChar = typeof(char).GetMethod(nameof(char.ToString), new[] { typeof(char) });
  91. il.Emit(OpCodes.Call, strFromChar);
  92. }
  93. il.Emit(OpCodes.Call, TextCreate);
  94. }
  95. else if (targetType == typeof(Boolean))
  96. {
  97. var BoolCreate = typeof(Value).GetMethod(nameof(Value.Bool));
  98. il.Emit(OpCodes.Call, BoolCreate);
  99. }
  100. else if (targetType == typeof(Integer))
  101. {
  102. var IntCreate = typeof(Value).GetMethod(nameof(Value.Integer));
  103. EmitNumberConvertTo(il, IntCreate.GetParameters()[0].ParameterType, member.Type);
  104. il.Emit(OpCodes.Call, IntCreate);
  105. }
  106. else if (targetType == typeof(FloatingPoint))
  107. {
  108. var FloatCreate = typeof(Value).GetMethod(nameof(Value.Float));
  109. EmitNumberConvertTo(il, FloatCreate.GetParameters()[0].ParameterType, member.Type);
  110. il.Emit(OpCodes.Call, FloatCreate);
  111. }
  112. else if (targetType == typeof(List))
  113. {
  114. // TODO: impl this (enumerables)
  115. Logger.config.Warn($"Implicit conversions to {targetType} are not currently implemented");
  116. il.Emit(OpCodes.Pop);
  117. il.Emit(OpCodes.Ldnull);
  118. }
  119. else if (targetType == typeof(Map))
  120. {
  121. // TODO: support other aggregate types
  122. if (!memberConversionType.IsValueType)
  123. {
  124. // if it is a reference type, we assume that its a generated type implementing IGeneratedStore
  125. var IGeneratedStore_Serialize = typeof(IGeneratedStore).GetMethod(nameof(IGeneratedStore.Serialize));
  126. var IGeneratedStoreT_CopyFrom = typeof(IGeneratedStore<>).MakeGenericType(member.Type)
  127. .GetMethod(nameof(IGeneratedStore<object>.CopyFrom));
  128. if (!member.IsVirtual)
  129. {
  130. var noCreate = il.DefineLabel();
  131. using var stlocal = GetLocal.Allocate(member.Type);
  132. // first check to make sure that this is an IGeneratedStore, because we don't control assignments to it
  133. il.Emit(OpCodes.Dup);
  134. il.Emit(OpCodes.Isinst, typeof(IGeneratedStore));
  135. il.Emit(OpCodes.Brtrue_S, noCreate);
  136. il.Emit(OpCodes.Stloc, stlocal);
  137. EmitCreateChildGenerated(il, member.Type, parentobj);
  138. il.Emit(OpCodes.Dup);
  139. il.Emit(OpCodes.Ldloc, stlocal);
  140. il.Emit(OpCodes.Ldc_I4_0);
  141. il.Emit(OpCodes.Callvirt, IGeneratedStoreT_CopyFrom);
  142. il.Emit(OpCodes.Dup);
  143. il.Emit(OpCodes.Stloc, stlocal);
  144. EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, stlocal), thisarg);
  145. il.MarkLabel(noCreate);
  146. }
  147. il.Emit(OpCodes.Callvirt, IGeneratedStore_Serialize);
  148. }
  149. else
  150. { // generate serialization for value types
  151. using var valueLocal = GetLocal.Allocate(memberConversionType);
  152. var structure = ReadObjectMembers(memberConversionType);
  153. if (!structure.Any())
  154. {
  155. Logger.config.Warn($"Custom value type {memberConversionType.FullName} (when compiling serialization of" +
  156. $" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members");
  157. il.Emit(OpCodes.Pop);
  158. }
  159. else
  160. {
  161. il.Emit(OpCodes.Stloc, valueLocal);
  162. }
  163. EmitSerializeStructure(il, structure, GetLocal, il => il.Emit(OpCodes.Ldloca, valueLocal), parentobj);
  164. }
  165. }
  166. il.MarkLabel(endSerialize);
  167. }
  168. private static void EmitSerializeStructure(ILGenerator il, IEnumerable<SerializedMemberInfo> structure, LocalAllocator GetLocal, Action<ILGenerator> thisarg, Action<ILGenerator> parentobj)
  169. {
  170. var MapCreate = typeof(Value).GetMethod(nameof(Value.Map));
  171. var MapAdd = typeof(Map).GetMethod(nameof(Map.Add));
  172. using var mapLocal = GetLocal.Allocate(typeof(Map));
  173. using var valueLocal = GetLocal.Allocate(typeof(Value));
  174. il.Emit(OpCodes.Call, MapCreate);
  175. il.Emit(OpCodes.Stloc, mapLocal);
  176. foreach (var mem in structure)
  177. {
  178. EmitSerializeMember(il, mem, GetLocal, thisarg, parentobj);
  179. il.Emit(OpCodes.Stloc, valueLocal);
  180. il.Emit(OpCodes.Ldloc, mapLocal);
  181. il.Emit(OpCodes.Ldstr, mem.Name);
  182. il.Emit(OpCodes.Ldloc, valueLocal);
  183. il.Emit(OpCodes.Call, MapAdd);
  184. }
  185. il.Emit(OpCodes.Ldloc, mapLocal);
  186. }
  187. }
  188. }