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.

1124 lines
50 KiB

  1. using IPA.Config.Data;
  2. using IPA.Config.Stores.Attributes;
  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;
  11. using System.Threading.Tasks;
  12. using System.Linq.Expressions;
  13. using System.Runtime.CompilerServices;
  14. using System.IO;
  15. using Boolean = IPA.Config.Data.Boolean;
  16. using System.Collections;
  17. using IPA.Utilities;
  18. #if NET3
  19. using Net3_Proxy;
  20. using Array = Net3_Proxy.Array;
  21. #endif
  22. [assembly: InternalsVisibleTo(IPA.Config.Stores.GeneratedExtension.AssemblyVisibilityTarget)]
  23. namespace IPA.Config.Stores
  24. {
  25. /// <summary>
  26. /// A class providing an extension for <see cref="Config"/> to make it easy to use generated
  27. /// config stores.
  28. /// </summary>
  29. public static class GeneratedExtension
  30. {
  31. /// <summary>
  32. /// The name of the assembly that internals must be visible to to allow internal protection.
  33. /// </summary>
  34. public const string AssemblyVisibilityTarget = GeneratedStore.GeneratedAssemblyName;
  35. /// <summary>
  36. /// Creates a generated <see cref="IConfigStore"/> of type <typeparamref name="T"/>, registers it to
  37. /// the <see cref="Config"/> object, and returns it. This also forces a synchronous config load via
  38. /// <see cref="Config.LoadSync"/> if <paramref name="loadSync"/> is <see langword="true"/>.
  39. /// </summary>
  40. /// <remarks>
  41. /// <para>
  42. /// <typeparamref name="T"/> must be a public non-<see langword="sealed"/> class.
  43. /// It can also be internal, but in that case, then your assembly must have the following attribute
  44. /// to allow the generated code to reference it.
  45. /// <code>
  46. /// [assembly: InternalsVisibleTo(IPA.Config.Stores.GeneratedExtension.AssemblyVisibilityTarget)]
  47. /// </code>
  48. /// </para>
  49. /// <para>
  50. /// Only fields and properties that are public or protected will be considered, and only properties
  51. /// where both the getter and setter are public or protected are considered. Any fields or properties
  52. /// with an <see cref="IgnoreAttribute"/> applied to them are also ignored. Having properties be <see langword="virtual"/> is not strictly
  53. /// necessary, however it allows the generated type to keep track of changes and lock around them so that the config will auto-save.
  54. /// </para>
  55. /// <para>
  56. /// All of the attributes in the <see cref="Attributes"/> namespace are handled as described by them.
  57. /// </para>
  58. /// <para>
  59. /// If the <typeparamref name="T"/> declares a public or protected, <see langword="virtual"/>
  60. /// method <c>Changed()</c>, then that method may be called to artificially signal to the runtime that the content of the object
  61. /// has changed. That method will also be called after the write locks are released when a property is set anywhere in the owning
  62. /// tree. This will only be called on the outermost generated object of the config structure, even if the change being signaled
  63. /// is somewhere deep into the tree.
  64. /// </para>
  65. /// <para>
  66. /// Similarly, <typeparamref name="T"/> can declare a public or protected, <see langword="virtual"/>
  67. /// method <c>OnReload()</c>, which will be called on the filesystem reader thread after the object has been repopulated with new data
  68. /// values. It will be called <i>after</i> the write lock for this object is released. This will only be called on the outermost generated
  69. /// object of the config structure.
  70. /// </para>
  71. /// <para>
  72. /// TODO: describe details of generated stores
  73. /// </para>
  74. /// </remarks>
  75. /// <typeparam name="T">the type to wrap</typeparam>
  76. /// <param name="cfg">the <see cref="Config"/> to register to</param>
  77. /// <param name="loadSync">whether to synchronously load the content, or trigger an async load</param>
  78. /// <returns>a generated instance of <typeparamref name="T"/> as a special <see cref="IConfigStore"/></returns>
  79. public static T Generated<T>(this Config cfg, bool loadSync = true) where T : class
  80. {
  81. var ret = GeneratedStore.Create<T>();
  82. cfg.SetStore(ret as IConfigStore);
  83. if (loadSync)
  84. cfg.LoadSync();
  85. else
  86. cfg.LoadAsync();
  87. return ret;
  88. }
  89. }
  90. internal static class GeneratedStore
  91. {
  92. internal interface IGeneratedStore
  93. {
  94. Type Type { get; }
  95. IGeneratedStore Parent { get; }
  96. Impl Impl { get; }
  97. void OnReload();
  98. Value Serialize();
  99. void Deserialize(Value val);
  100. }
  101. internal class Impl : IConfigStore
  102. {
  103. private IGeneratedStore generated;
  104. internal static ConstructorInfo Ctor = typeof(Impl).GetConstructor(new[] { typeof(IGeneratedStore) });
  105. public Impl(IGeneratedStore store) => generated = store;
  106. private readonly AutoResetEvent resetEvent = new AutoResetEvent(false);
  107. public WaitHandle SyncObject => resetEvent;
  108. internal static MethodInfo SyncObjectGetMethod = typeof(Impl).GetProperty(nameof(SyncObject)).GetGetMethod();
  109. public ReaderWriterLockSlim WriteSyncObject { get; } = new ReaderWriterLockSlim();
  110. internal static MethodInfo WriteSyncObjectGetMethod = typeof(Impl).GetProperty(nameof(WriteSyncObject)).GetGetMethod();
  111. internal static MethodInfo ImplSignalChangedMethod = typeof(Impl).GetMethod(nameof(ImplSignalChanged));
  112. public static void ImplSignalChanged(IGeneratedStore s) => FindImpl(s).SignalChanged();
  113. public void SignalChanged() => resetEvent.Set();
  114. internal static MethodInfo ImplTakeReadMethod = typeof(Impl).GetMethod(nameof(ImplTakeRead));
  115. public static void ImplTakeRead(IGeneratedStore s) => FindImpl(s).TakeRead();
  116. public void TakeRead() => WriteSyncObject.EnterReadLock();
  117. internal static MethodInfo ImplReleaseReadMethod = typeof(Impl).GetMethod(nameof(ImplReleaseRead));
  118. public static void ImplReleaseRead(IGeneratedStore s) => FindImpl(s).ReleaseRead();
  119. public void ReleaseRead() => WriteSyncObject.ExitReadLock();
  120. internal static MethodInfo ImplTakeWriteMethod = typeof(Impl).GetMethod(nameof(ImplTakeWrite));
  121. public static void ImplTakeWrite(IGeneratedStore s) => FindImpl(s).TakeWrite();
  122. public void TakeWrite() => WriteSyncObject.EnterWriteLock();
  123. internal static MethodInfo ImplReleaseWriteMethod = typeof(Impl).GetMethod(nameof(ImplReleaseWrite));
  124. public static void ImplReleaseWrite(IGeneratedStore s) => FindImpl(s).ReleaseWrite();
  125. public void ReleaseWrite() => WriteSyncObject.ExitWriteLock();
  126. internal static MethodInfo FindImplMethod = typeof(Impl).GetMethod(nameof(FindImpl));
  127. public static Impl FindImpl(IGeneratedStore store)
  128. {
  129. while (store?.Parent != null) store = store.Parent; // walk to the top of the tree
  130. return store?.Impl;
  131. }
  132. internal static MethodInfo ReadFromMethod = typeof(Impl).GetMethod(nameof(ReadFrom));
  133. public void ReadFrom(IConfigProvider provider)
  134. {
  135. var values = provider.Load();
  136. Logger.config.Debug("Generated impl ReadFrom");
  137. Logger.config.Debug($"Read {values}");
  138. generated.Deserialize(values);
  139. ReleaseWrite();
  140. generated.OnReload();
  141. TakeWrite(); // must take again for runtime to be happy (which is unfortunate)
  142. }
  143. internal static MethodInfo WriteToMethod = typeof(Impl).GetMethod(nameof(WriteTo));
  144. public void WriteTo(IConfigProvider provider)
  145. {
  146. var values = generated.Serialize();
  147. Logger.config.Debug("Generated impl WriteTo");
  148. Logger.config.Debug($"Serialized {values}");
  149. provider.Store(values);
  150. }
  151. }
  152. private static Dictionary<Type, Func<IGeneratedStore, IConfigStore>> generatedCreators = new Dictionary<Type, Func<IGeneratedStore, IConfigStore>>();
  153. private static Dictionary<Type, Dictionary<string, Type>> memberMaps = new Dictionary<Type, Dictionary<string, Type>>();
  154. public static T Create<T>() where T : class => (T)Create(typeof(T));
  155. public static IConfigStore Create(Type type) => Create(type, null);
  156. private static readonly MethodInfo CreateGParent =
  157. typeof(GeneratedStore).GetMethod(nameof(Create), BindingFlags.NonPublic | BindingFlags.Static, null,
  158. CallingConventions.Any, new[] { typeof(IGeneratedStore) }, Array.Empty<ParameterModifier>());
  159. internal static T Create<T>(IGeneratedStore parent) where T : class => (T)Create(typeof(T), parent);
  160. private static IConfigStore Create(Type type, IGeneratedStore parent)
  161. {
  162. if (generatedCreators.TryGetValue(type, out var creator))
  163. return creator(parent);
  164. else
  165. {
  166. creator = MakeCreator(type);
  167. generatedCreators.Add(type, creator);
  168. return creator(parent);
  169. }
  170. }
  171. internal const string GeneratedAssemblyName = "IPA.Config.Generated";
  172. private static AssemblyBuilder assembly = null;
  173. private static AssemblyBuilder Assembly
  174. {
  175. get
  176. {
  177. if (assembly == null)
  178. {
  179. var name = new AssemblyName(GeneratedAssemblyName);
  180. assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave);
  181. }
  182. return assembly;
  183. }
  184. }
  185. internal static void DebugSaveAssembly(string file)
  186. {
  187. Assembly.Save(file);
  188. }
  189. private static ModuleBuilder module = null;
  190. private static ModuleBuilder Module
  191. {
  192. get
  193. {
  194. if (module == null)
  195. module = Assembly.DefineDynamicModule(Assembly.GetName().Name, Assembly.GetName().Name + ".dll");
  196. return module;
  197. }
  198. }
  199. private struct SerializedMemberInfo
  200. {
  201. public string Name;
  202. public MemberInfo Member;
  203. public Type Type;
  204. public bool AllowNull;
  205. public bool IsVirtual;
  206. public bool IsField;
  207. public bool IsNullable; // signifies whether this is a Nullable<T>
  208. // invalid for objects with IsNullabe false
  209. public Type NullableWrappedType => Nullable.GetUnderlyingType(Type);
  210. // invalid for objects with IsNullabe false
  211. public PropertyInfo Nullable_HasValue => Type.GetProperty(nameof(Nullable<int>.HasValue));
  212. // invalid for objects with IsNullabe false
  213. public PropertyInfo Nullable_Value => Type.GetProperty(nameof(Nullable<int>.Value));
  214. // invalid for objects with IsNullabe false
  215. public ConstructorInfo Nullable_Construct => Type.GetConstructor(new[] { NullableWrappedType });
  216. }
  217. private static Func<IGeneratedStore, IConfigStore> MakeCreator(Type type)
  218. {
  219. var baseCtor = type.GetConstructor(Type.EmptyTypes); // get a default constructor
  220. if (baseCtor == null)
  221. throw new ArgumentException("Config type does not have a public parameterless constructor");
  222. var typeBuilder = Module.DefineType($"{type.FullName}<Generated>",
  223. TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, type);
  224. var typeField = typeBuilder.DefineField("<>_type", typeof(Type), FieldAttributes.Private | FieldAttributes.InitOnly);
  225. var implField = typeBuilder.DefineField("<>_impl", typeof(Impl), FieldAttributes.Private | FieldAttributes.InitOnly);
  226. var parentField = typeBuilder.DefineField("<>_parent", typeof(IGeneratedStore), FieldAttributes.Private | FieldAttributes.InitOnly);
  227. // none of this can be Expressions because CompileToMethod requires a static target method for some dumbass reason
  228. #region Parse base object structure
  229. var baseChanged = type.GetMethod("Changed", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, Array.Empty<ParameterModifier>());
  230. if (baseChanged != null && !baseChanged.IsVirtual) baseChanged = null; // limit this to just the one thing
  231. var baseOnReload = type.GetMethod("OnReload", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, Array.Empty<ParameterModifier>());
  232. if (baseOnReload != null && !baseOnReload.IsVirtual) baseOnReload = null; // limit this to just the one thing
  233. var structure = new List<SerializedMemberInfo>();
  234. // TODO: support converters
  235. bool ProcessAttributesFor(MemberInfo member, Type memberType, out string name, out bool allowNull, out bool isNullable)
  236. {
  237. var attrs = member.GetCustomAttributes(true);
  238. var ignores = attrs.Select(o => o as IgnoreAttribute).NonNull();
  239. if (ignores.Any()) // we ignore
  240. {
  241. name = null;
  242. allowNull = false;
  243. isNullable = false;
  244. return false;
  245. }
  246. var nonNullables = attrs.Select(o => o as NonNullableAttribute).NonNull();
  247. name = member.Name;
  248. isNullable = memberType.IsGenericType
  249. && memberType.GetGenericTypeDefinition() == typeof(Nullable<>);
  250. allowNull = !nonNullables.Any() && (!memberType.IsValueType || isNullable);
  251. var nameAttr = attrs.Select(o => o as SerializedNameAttribute).NonNull().FirstOrDefault();
  252. if (nameAttr != null)
  253. name = nameAttr.Name;
  254. return true;
  255. }
  256. // only looks at public/protected properties
  257. foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
  258. {
  259. if (prop.GetSetMethod(true)?.IsPrivate ?? true)
  260. { // we enter this block if the setter is inacessible or doesn't exist
  261. continue; // ignore props without setter
  262. }
  263. if (prop.GetGetMethod(true)?.IsPrivate ?? true)
  264. { // we enter this block if the getter is inacessible or doesn't exist
  265. continue; // ignore props without getter
  266. }
  267. var smi = new SerializedMemberInfo
  268. {
  269. Member = prop,
  270. IsVirtual = (prop.GetGetMethod(true)?.IsVirtual ?? false) ||
  271. (prop.GetSetMethod(true)?.IsVirtual ?? false),
  272. IsField = false,
  273. Type = prop.PropertyType
  274. };
  275. if (!ProcessAttributesFor(smi.Member, smi.Type, out smi.Name, out smi.AllowNull, out smi.IsNullable)) continue;
  276. structure.Add(smi);
  277. }
  278. // only look at public/protected fields
  279. foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
  280. {
  281. if (field.IsPrivate) continue;
  282. var smi = new SerializedMemberInfo
  283. {
  284. Member = field,
  285. IsVirtual = false,
  286. IsField = true,
  287. Type = field.FieldType
  288. };
  289. if (!ProcessAttributesFor(smi.Member, smi.Type, out smi.Name, out smi.AllowNull, out smi.IsNullable)) continue;
  290. structure.Add(smi);
  291. }
  292. #endregion
  293. #region Constructor
  294. var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(IGeneratedStore) });
  295. { // because this is a constructor, it has to be raw IL
  296. var il = ctor.GetILGenerator();
  297. il.Emit(OpCodes.Ldarg_0); // keep this at bottom of stack
  298. il.Emit(OpCodes.Dup);
  299. il.Emit(OpCodes.Call, baseCtor);
  300. il.Emit(OpCodes.Dup);
  301. il.Emit(OpCodes.Ldarg_1); // load parent
  302. il.Emit(OpCodes.Stfld, parentField);
  303. il.Emit(OpCodes.Dup);
  304. EmitTypeof(il, type);
  305. il.Emit(OpCodes.Stfld, typeField);
  306. il.Emit(OpCodes.Dup);
  307. il.Emit(OpCodes.Dup);
  308. il.Emit(OpCodes.Newobj, Impl.Ctor);
  309. il.Emit(OpCodes.Stfld, implField);
  310. foreach (var member in structure)
  311. EmitMemberFix(il, member);
  312. il.Emit(OpCodes.Pop);
  313. il.Emit(OpCodes.Ret);
  314. }
  315. #endregion
  316. const MethodAttributes propertyMethodAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
  317. const MethodAttributes virtualPropertyMethodAttr = propertyMethodAttr | MethodAttributes.Virtual | MethodAttributes.Final;
  318. const MethodAttributes virtualMemberMethod = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.Final;
  319. #region IGeneratedStore
  320. typeBuilder.AddInterfaceImplementation(typeof(IGeneratedStore));
  321. var IGeneratedStore_t = typeof(IGeneratedStore);
  322. var IGeneratedStore_GetImpl = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Impl)).GetGetMethod();
  323. var IGeneratedStore_GetType = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Type)).GetGetMethod();
  324. var IGeneratedStore_GetParent = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Parent)).GetGetMethod();
  325. var IGeneratedStore_Serialize = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.Serialize));
  326. var IGeneratedStore_Deserialize = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.Deserialize));
  327. var IGeneratedStore_OnReload = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.OnReload));
  328. #region IGeneratedStore.OnReload
  329. var onReload = typeBuilder.DefineMethod($"<>{nameof(IGeneratedStore.OnReload)}", virtualMemberMethod, null, Type.EmptyTypes);
  330. typeBuilder.DefineMethodOverride(onReload, IGeneratedStore_OnReload);
  331. if (baseOnReload != null) typeBuilder.DefineMethodOverride(onReload, baseOnReload);
  332. {
  333. var il = onReload.GetILGenerator();
  334. if (baseOnReload != null)
  335. {
  336. il.Emit(OpCodes.Ldarg_0); // load this
  337. il.Emit(OpCodes.Tailcall);
  338. il.Emit(OpCodes.Call, baseOnReload); // load impl field
  339. }
  340. il.Emit(OpCodes.Ret);
  341. }
  342. #endregion
  343. #region IGeneratedStore.Impl
  344. var implProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Impl), PropertyAttributes.None, typeof(Impl), null);
  345. var implPropGet = typeBuilder.DefineMethod($"<g>{nameof(IGeneratedStore.Impl)}", virtualPropertyMethodAttr, implProp.PropertyType, Type.EmptyTypes);
  346. implProp.SetGetMethod(implPropGet);
  347. typeBuilder.DefineMethodOverride(implPropGet, IGeneratedStore_GetImpl);
  348. {
  349. var il = implPropGet.GetILGenerator();
  350. il.Emit(OpCodes.Ldarg_0); // load this
  351. il.Emit(OpCodes.Ldfld, implField); // load impl field
  352. il.Emit(OpCodes.Ret);
  353. }
  354. #endregion
  355. #region IGeneratedStore.Type
  356. var typeProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Type), PropertyAttributes.None, typeof(Type), null);
  357. var typePropGet = typeBuilder.DefineMethod($"<g>{nameof(IGeneratedStore.Type)}", virtualPropertyMethodAttr, typeProp.PropertyType, Type.EmptyTypes);
  358. typeProp.SetGetMethod(typePropGet);
  359. typeBuilder.DefineMethodOverride(typePropGet, IGeneratedStore_GetType);
  360. {
  361. var il = typePropGet.GetILGenerator();
  362. il.Emit(OpCodes.Ldarg_0); // load this
  363. il.Emit(OpCodes.Ldfld, typeField); // load impl field
  364. il.Emit(OpCodes.Ret);
  365. }
  366. #endregion
  367. #region IGeneratedStore.Parent
  368. var parentProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Parent), PropertyAttributes.None, typeof(IGeneratedStore), null);
  369. var parentPropGet = typeBuilder.DefineMethod($"<g>{nameof(IGeneratedStore.Parent)}", virtualPropertyMethodAttr, parentProp.PropertyType, Type.EmptyTypes);
  370. parentProp.SetGetMethod(parentPropGet);
  371. typeBuilder.DefineMethodOverride(parentPropGet, IGeneratedStore_GetParent);
  372. {
  373. var il = parentPropGet.GetILGenerator();
  374. il.Emit(OpCodes.Ldarg_0); // load this
  375. il.Emit(OpCodes.Ldfld, parentField); // load impl field
  376. il.Emit(OpCodes.Ret);
  377. }
  378. #endregion
  379. #region IGeneratedStore.Serialize
  380. var serializeGen = typeBuilder.DefineMethod($"<>{nameof(IGeneratedStore.Serialize)}", virtualPropertyMethodAttr, IGeneratedStore_Serialize.ReturnType, Type.EmptyTypes);
  381. typeBuilder.DefineMethodOverride(serializeGen, IGeneratedStore_Serialize);
  382. { // this is non-locking because the only code that will call this will already own the correct lock
  383. var il = serializeGen.GetILGenerator();
  384. var Map_Add = typeof(Map).GetMethod(nameof(Map.Add));
  385. il.Emit(OpCodes.Call, typeof(Value).GetMethod(nameof(Value.Map)));
  386. // the map is now at the top of the stack
  387. var locals = new List<LocalBuilder>();
  388. LocalBuilder GetLocal(Type ty, int i = 0)
  389. {
  390. var builder = locals.Where(b => b.LocalType == ty).Skip(i).FirstOrDefault();
  391. if (builder == null)
  392. {
  393. builder = il.DeclareLocal(ty);
  394. locals.Add(builder);
  395. }
  396. return builder;
  397. }
  398. foreach (var member in structure)
  399. {
  400. il.Emit(OpCodes.Dup);
  401. il.Emit(OpCodes.Ldstr, member.Name); // TODO: make this behave with annotations
  402. EmitSerializeMember(il, member, GetLocal);
  403. il.Emit(OpCodes.Call, Map_Add);
  404. }
  405. // the map is still at the top of the stack, return it
  406. il.Emit(OpCodes.Ret);
  407. }
  408. #endregion
  409. #region IGeneratedStore.Deserialize
  410. var deserializeGen = typeBuilder.DefineMethod($"<>{nameof(IGeneratedStore.Deserialize)}", virtualPropertyMethodAttr, null,
  411. new[] { IGeneratedStore_Deserialize.GetParameters()[0].ParameterType });
  412. typeBuilder.DefineMethodOverride(deserializeGen, IGeneratedStore_Deserialize);
  413. { // this is non-locking because the only code that will call this will already own the correct lock
  414. var il = deserializeGen.GetILGenerator();
  415. var Map_t = typeof(Map);
  416. var Map_TryGetValue = Map_t.GetMethod(nameof(Map.TryGetValue));
  417. var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType));
  418. var valueLocal = il.DeclareLocal(typeof(Value));
  419. var nonNull = il.DefineLabel();
  420. il.Emit(OpCodes.Ldarg_1);
  421. il.Emit(OpCodes.Brtrue, nonNull);
  422. EmitLogError(il, "Attempting to deserialize null", tailcall: true);
  423. il.Emit(OpCodes.Ret);
  424. il.MarkLabel(nonNull);
  425. il.Emit(OpCodes.Ldarg_1);
  426. il.Emit(OpCodes.Isinst, Map_t);
  427. il.Emit(OpCodes.Dup); // duplicate cloned value
  428. var notMapError = il.DefineLabel();
  429. il.Emit(OpCodes.Brtrue, notMapError);
  430. // handle error
  431. il.Emit(OpCodes.Pop); // removes the duplicate value
  432. EmitLogError(il, $"Invalid root for deserializing {type.FullName}", tailcall: true,
  433. expected: il => EmitTypeof(il, Map_t), found: il =>
  434. {
  435. il.Emit(OpCodes.Ldarg_1);
  436. il.Emit(OpCodes.Callvirt, Object_GetType);
  437. });
  438. il.Emit(OpCodes.Ret);
  439. var nextLabel = notMapError;
  440. var locals = new List<LocalBuilder>();
  441. LocalBuilder GetLocal(Type ty, int i = 0)
  442. {
  443. var builder = locals.Where(b => b.LocalType == ty).Skip(i).FirstOrDefault();
  444. if (builder == null)
  445. {
  446. builder = il.DeclareLocal(ty);
  447. locals.Add(builder);
  448. }
  449. return builder;
  450. }
  451. // head of stack is Map instance
  452. foreach (var member in structure)
  453. {
  454. il.MarkLabel(nextLabel);
  455. nextLabel = il.DefineLabel();
  456. var endErrorLabel = il.DefineLabel();
  457. il.Emit(OpCodes.Dup);
  458. il.Emit(OpCodes.Ldstr, member.Name);
  459. il.Emit(OpCodes.Ldloca_S, valueLocal);
  460. il.Emit(OpCodes.Call, Map_TryGetValue);
  461. il.Emit(OpCodes.Brtrue_S, endErrorLabel);
  462. EmitLogError(il, $"Missing key {member.Name}", tailcall: false);
  463. il.Emit(OpCodes.Br, nextLabel);
  464. il.MarkLabel(endErrorLabel);
  465. il.Emit(OpCodes.Ldloc_S, valueLocal);
  466. EmitDeserializeMember(il, member, nextLabel, il => il.Emit(OpCodes.Ldloc_S, valueLocal), GetLocal);
  467. }
  468. il.MarkLabel(nextLabel);
  469. il.Emit(OpCodes.Pop); // removes the duplicate value
  470. il.Emit(OpCodes.Ret);
  471. }
  472. #endregion
  473. #endregion
  474. #region IConfigStore
  475. typeBuilder.AddInterfaceImplementation(typeof(IConfigStore));
  476. var IConfigStore_t = typeof(IConfigStore);
  477. var IConfigStore_GetSyncObject = IConfigStore_t.GetProperty(nameof(IConfigStore.SyncObject)).GetGetMethod();
  478. var IConfigStore_GetWriteSyncObject = IConfigStore_t.GetProperty(nameof(IConfigStore.WriteSyncObject)).GetGetMethod();
  479. var IConfigStore_WriteTo = IConfigStore_t.GetMethod(nameof(IConfigStore.WriteTo));
  480. var IConfigStore_ReadFrom = IConfigStore_t.GetMethod(nameof(IConfigStore.ReadFrom));
  481. #region IConfigStore.SyncObject
  482. var syncObjProp = typeBuilder.DefineProperty(nameof(IConfigStore.SyncObject), PropertyAttributes.None, typeof(WaitHandle), null);
  483. var syncObjPropGet = typeBuilder.DefineMethod($"<g>{nameof(IConfigStore.SyncObject)}", virtualPropertyMethodAttr, syncObjProp.PropertyType, Type.EmptyTypes);
  484. syncObjProp.SetGetMethod(syncObjPropGet);
  485. typeBuilder.DefineMethodOverride(syncObjPropGet, IConfigStore_GetSyncObject);
  486. {
  487. var il = syncObjPropGet.GetILGenerator();
  488. il.Emit(OpCodes.Ldarg_0);
  489. il.Emit(OpCodes.Call, Impl.FindImplMethod);
  490. il.Emit(OpCodes.Tailcall);
  491. il.Emit(OpCodes.Call, Impl.SyncObjectGetMethod);
  492. il.Emit(OpCodes.Ret);
  493. }
  494. #endregion
  495. #region IConfigStore.WriteSyncObject
  496. var writeSyncObjProp = typeBuilder.DefineProperty(nameof(IConfigStore.WriteSyncObject), PropertyAttributes.None, typeof(WaitHandle), null);
  497. var writeSyncObjPropGet = typeBuilder.DefineMethod($"<g>{nameof(IConfigStore.WriteSyncObject)}", virtualPropertyMethodAttr, writeSyncObjProp.PropertyType, Type.EmptyTypes);
  498. writeSyncObjProp.SetGetMethod(writeSyncObjPropGet);
  499. typeBuilder.DefineMethodOverride(writeSyncObjPropGet, IConfigStore_GetWriteSyncObject);
  500. {
  501. var il = writeSyncObjPropGet.GetILGenerator();
  502. il.Emit(OpCodes.Ldarg_0);
  503. il.Emit(OpCodes.Call, Impl.FindImplMethod);
  504. il.Emit(OpCodes.Tailcall);
  505. il.Emit(OpCodes.Call, Impl.WriteSyncObjectGetMethod);
  506. il.Emit(OpCodes.Ret);
  507. }
  508. #endregion
  509. #region IConfigStore.WriteTo
  510. var writeTo = typeBuilder.DefineMethod($"<>{nameof(IConfigStore.WriteTo)}", virtualMemberMethod, null, new[] { typeof(IConfigProvider) });
  511. typeBuilder.DefineMethodOverride(writeTo, IConfigStore_WriteTo);
  512. {
  513. var il = writeTo.GetILGenerator();
  514. il.Emit(OpCodes.Ldarg_0);
  515. il.Emit(OpCodes.Call, Impl.FindImplMethod);
  516. il.Emit(OpCodes.Ldarg_1);
  517. il.Emit(OpCodes.Tailcall);
  518. il.Emit(OpCodes.Call, Impl.WriteToMethod);
  519. il.Emit(OpCodes.Ret);
  520. }
  521. #endregion
  522. #region IConfigStore.ReadFrom
  523. var readFrom = typeBuilder.DefineMethod($"<>{nameof(IConfigStore.ReadFrom)}", virtualMemberMethod, null, new[] { typeof(IConfigProvider) });
  524. typeBuilder.DefineMethodOverride(readFrom, IConfigStore_ReadFrom);
  525. {
  526. var il = readFrom.GetILGenerator();
  527. il.Emit(OpCodes.Ldarg_0);
  528. il.Emit(OpCodes.Call, Impl.FindImplMethod);
  529. il.Emit(OpCodes.Ldarg_1);
  530. il.Emit(OpCodes.Tailcall);
  531. il.Emit(OpCodes.Call, Impl.ReadFromMethod);
  532. il.Emit(OpCodes.Ret);
  533. }
  534. #endregion
  535. #endregion
  536. #region Changed
  537. var coreChanged = typeBuilder.DefineMethod(
  538. "<>Changed",
  539. MethodAttributes.Public | MethodAttributes.HideBySig,
  540. null, Type.EmptyTypes);
  541. {
  542. var il = coreChanged.GetILGenerator();
  543. il.Emit(OpCodes.Ldarg_0);
  544. il.Emit(OpCodes.Call, Impl.ImplSignalChangedMethod);
  545. il.Emit(OpCodes.Ret); // simply call our impl's SignalChanged method and return
  546. }
  547. if (baseChanged != null) {
  548. var changedMethod = typeBuilder.DefineMethod( // copy to override baseChanged
  549. baseChanged.Name,
  550. MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig,
  551. null, Type.EmptyTypes);
  552. typeBuilder.DefineMethodOverride(changedMethod, baseChanged);
  553. {
  554. var il = changedMethod.GetILGenerator();
  555. il.Emit(OpCodes.Ldarg_0);
  556. il.Emit(OpCodes.Call, baseChanged); // call base
  557. il.Emit(OpCodes.Ldarg_0);
  558. il.Emit(OpCodes.Tailcall);
  559. il.Emit(OpCodes.Call, coreChanged); // call back to the core change method
  560. il.Emit(OpCodes.Ret);
  561. }
  562. coreChanged = changedMethod; // switch to calling this version instead of just the default
  563. }
  564. #endregion
  565. // TODO: generate overrides for all the virtual properties
  566. var genType = typeBuilder.CreateType();
  567. var parentParam = Expression.Parameter(typeof(IGeneratedStore), "parent");
  568. var creatorDel = Expression.Lambda<Func<IGeneratedStore, IConfigStore>>(
  569. Expression.New(ctor, parentParam), parentParam
  570. ).Compile();
  571. { // register a member map
  572. var dict = new Dictionary<string, Type>();
  573. foreach (var member in structure)
  574. dict.Add(member.Name, member.Type);
  575. memberMaps.Add(type, dict);
  576. }
  577. return creatorDel;
  578. }
  579. #region Utility
  580. private static void EmitLogError(ILGenerator il, string message, bool tailcall = false, Action<ILGenerator> expected = null, Action<ILGenerator> found = null)
  581. {
  582. if (expected == null) expected = il => il.Emit(OpCodes.Ldnull);
  583. if (found == null) found = il => il.Emit(OpCodes.Ldnull);
  584. expected(il);
  585. found(il);
  586. il.Emit(OpCodes.Ldstr, message);
  587. if (tailcall) il.Emit(OpCodes.Tailcall);
  588. il.Emit(OpCodes.Call, LogErrorMethod);
  589. }
  590. private static readonly MethodInfo Type_GetTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle));
  591. private static void EmitTypeof(ILGenerator il, Type type)
  592. {
  593. il.Emit(OpCodes.Ldtoken, type);
  594. il.Emit(OpCodes.Call, Type_GetTypeFromHandle);
  595. }
  596. private static Type Decimal_t = typeof(decimal);
  597. private static ConstructorInfo Decimal_FromFloat = Decimal_t.GetConstructor(new[] { typeof(float) });
  598. private static ConstructorInfo Decimal_FromDouble = Decimal_t.GetConstructor(new[] { typeof(double) });
  599. private static ConstructorInfo Decimal_FromInt = Decimal_t.GetConstructor(new[] { typeof(int) });
  600. private static ConstructorInfo Decimal_FromUInt = Decimal_t.GetConstructor(new[] { typeof(uint) });
  601. private static ConstructorInfo Decimal_FromLong = Decimal_t.GetConstructor(new[] { typeof(long) });
  602. private static ConstructorInfo Decimal_FromULong = Decimal_t.GetConstructor(new[] { typeof(ulong) });
  603. private static void EmitNumberConvertTo(ILGenerator il, Type to, Type from)
  604. { // WARNING: THIS USES THE NO-OVERFLOW OPCODES
  605. if (to == from) return;
  606. if (to == Decimal_t)
  607. {
  608. if (from == typeof(float)) il.Emit(OpCodes.Newobj, Decimal_FromFloat);
  609. else if (from == typeof(double)) il.Emit(OpCodes.Newobj, Decimal_FromDouble);
  610. else if (from == typeof(long)) il.Emit(OpCodes.Newobj, Decimal_FromLong);
  611. else if (from == typeof(ulong)) il.Emit(OpCodes.Newobj, Decimal_FromULong);
  612. else if (from == typeof(int)) il.Emit(OpCodes.Newobj, Decimal_FromInt);
  613. else if (from == typeof(uint)) il.Emit(OpCodes.Newobj, Decimal_FromUInt);
  614. else if (from == typeof(IntPtr))
  615. {
  616. EmitNumberConvertTo(il, typeof(long), from);
  617. EmitNumberConvertTo(il, to, typeof(long));
  618. }
  619. else if (from == typeof(UIntPtr))
  620. {
  621. EmitNumberConvertTo(il, typeof(ulong), from);
  622. EmitNumberConvertTo(il, to, typeof(ulong));
  623. }
  624. else
  625. { // if the source is anything else, we first convert to int because that can contain all other values
  626. EmitNumberConvertTo(il, typeof(int), from);
  627. EmitNumberConvertTo(il, to, typeof(int));
  628. };
  629. }
  630. else if (from == Decimal_t)
  631. {
  632. if (to == typeof(IntPtr))
  633. {
  634. EmitNumberConvertTo(il, typeof(long), from);
  635. EmitNumberConvertTo(il, to, typeof(long));
  636. }
  637. else if (to == typeof(UIntPtr))
  638. {
  639. EmitNumberConvertTo(il, typeof(ulong), from);
  640. EmitNumberConvertTo(il, to, typeof(ulong));
  641. }
  642. else
  643. {
  644. var method = Decimal_t.GetMethod($"To{to.Name}"); // conveniently, this is the pattern of the to* names
  645. il.Emit(OpCodes.Call, method);
  646. }
  647. }
  648. else if (to == typeof(IntPtr)) il.Emit(OpCodes.Conv_I);
  649. else if (to == typeof(UIntPtr)) il.Emit(OpCodes.Conv_U);
  650. else if (to == typeof(sbyte)) il.Emit(OpCodes.Conv_I1);
  651. else if (to == typeof(byte)) il.Emit(OpCodes.Conv_U1);
  652. else if (to == typeof(short)) il.Emit(OpCodes.Conv_I2);
  653. else if (to == typeof(ushort)) il.Emit(OpCodes.Conv_U2);
  654. else if (to == typeof(int)) il.Emit(OpCodes.Conv_I4);
  655. else if (to == typeof(uint)) il.Emit(OpCodes.Conv_U4);
  656. else if (to == typeof(long)) il.Emit(OpCodes.Conv_I8);
  657. else if (to == typeof(ulong)) il.Emit(OpCodes.Conv_U8);
  658. else if (to == typeof(float))
  659. {
  660. if (from == typeof(byte)
  661. || from == typeof(ushort)
  662. || from == typeof(uint)
  663. || from == typeof(ulong)
  664. || from == typeof(UIntPtr)) il.Emit(OpCodes.Conv_R_Un);
  665. il.Emit(OpCodes.Conv_R4);
  666. }
  667. else if (to == typeof(double))
  668. {
  669. if (from == typeof(byte)
  670. || from == typeof(ushort)
  671. || from == typeof(uint)
  672. || from == typeof(ulong)
  673. || from == typeof(UIntPtr)) il.Emit(OpCodes.Conv_R_Un);
  674. il.Emit(OpCodes.Conv_R8);
  675. }
  676. }
  677. private static void EmitCreateChildGenerated(ILGenerator il, Type childType)
  678. {
  679. var method = CreateGParent.MakeGenericMethod(childType);
  680. il.Emit(OpCodes.Ldarg_0);
  681. il.Emit(OpCodes.Call, method);
  682. }
  683. #endregion
  684. private static readonly MethodInfo LogErrorMethod = typeof(GeneratedStore).GetMethod(nameof(LogError), BindingFlags.NonPublic | BindingFlags.Static);
  685. internal static void LogError(Type expected, Type found, string message)
  686. {
  687. Logger.config.Notice($"{message}{(expected == null ? "" : $" (expected {expected}, found {found?.ToString() ?? "null"})")}");
  688. }
  689. // expects the this param to be on the stack
  690. private static void EmitMemberFix(ILGenerator il, SerializedMemberInfo member)
  691. {
  692. // TODO: impl
  693. }
  694. #region Serialize
  695. // emit takes no args, leaves Value at top of stack
  696. private static void EmitSerializeMember(ILGenerator il, SerializedMemberInfo member, Func<Type, int, LocalBuilder> GetLocal)
  697. {
  698. void EmitLoad()
  699. {
  700. il.Emit(OpCodes.Ldarg_0); // load this
  701. if (member.IsField)
  702. il.Emit(OpCodes.Ldfld, member.Member as FieldInfo);
  703. else
  704. { // member is a property
  705. var prop = member.Member as PropertyInfo;
  706. var getter = prop.GetGetMethod();
  707. if (getter == null) throw new InvalidOperationException($"Property {member.Name} does not have a getter and is not ignored");
  708. il.Emit(OpCodes.Call, getter);
  709. }
  710. }
  711. EmitLoad();
  712. var endSerialize = il.DefineLabel();
  713. if (member.AllowNull)
  714. {
  715. var passedNull = il.DefineLabel();
  716. il.Emit(OpCodes.Dup);
  717. if (member.IsNullable)
  718. il.Emit(OpCodes.Call, member.Nullable_HasValue.GetGetMethod());
  719. il.Emit(OpCodes.Brtrue, passedNull);
  720. il.Emit(OpCodes.Pop);
  721. il.Emit(OpCodes.Ldnull);
  722. il.Emit(OpCodes.Br, endSerialize);
  723. il.MarkLabel(passedNull);
  724. }
  725. if (member.IsNullable)
  726. il.Emit(OpCodes.Call, member.Nullable_Value.GetGetMethod());
  727. var targetType = GetExpectedValueTypeForType(member.Type);
  728. if (targetType == typeof(Text))
  729. { // only happens when arg is a string or char
  730. var TextCreate = typeof(Value).GetMethod(nameof(Value.Text));
  731. if (member.Type == typeof(char))
  732. {
  733. var strFromChar = typeof(char).GetMethod(nameof(char.ToString), new[] { typeof(char) });
  734. il.Emit(OpCodes.Call, strFromChar);
  735. }
  736. il.Emit(OpCodes.Call, TextCreate);
  737. }
  738. else if (targetType == typeof(Boolean))
  739. {
  740. var BoolCreate = typeof(Value).GetMethod(nameof(Value.Bool));
  741. il.Emit(OpCodes.Call, BoolCreate);
  742. }
  743. else if (targetType == typeof(Integer))
  744. {
  745. var IntCreate = typeof(Value).GetMethod(nameof(Value.Integer));
  746. EmitNumberConvertTo(il, IntCreate.GetParameters()[0].ParameterType, member.Type);
  747. il.Emit(OpCodes.Call, IntCreate);
  748. }
  749. else if (targetType == typeof(FloatingPoint))
  750. {
  751. var FloatCreate = typeof(Value).GetMethod(nameof(Value.Float));
  752. EmitNumberConvertTo(il, FloatCreate.GetParameters()[0].ParameterType, member.Type);
  753. il.Emit(OpCodes.Call, FloatCreate);
  754. }
  755. else if (targetType == typeof(List))
  756. {
  757. // TODO: impl this
  758. il.Emit(OpCodes.Pop);
  759. il.Emit(OpCodes.Ldnull);
  760. }
  761. else if (targetType == typeof(Map))
  762. {
  763. // TODO: support other aggregate types
  764. // for now, we assume that its a generated type implementing IGeneratedStore
  765. var IGeneratedStore_Serialize = typeof(IGeneratedStore).GetMethod(nameof(IGeneratedStore.Serialize));
  766. il.Emit(OpCodes.Callvirt, IGeneratedStore_Serialize);
  767. }
  768. il.MarkLabel(endSerialize);
  769. // TODO: implement converters
  770. }
  771. #endregion
  772. #region Deserialize
  773. private static Type GetExpectedValueTypeForType(Type valT)
  774. {
  775. if (typeof(Value).IsAssignableFrom(valT)) // this is a Value subtype
  776. return valT;
  777. if (valT == typeof(string)
  778. || valT == typeof(char)) return typeof(Text);
  779. if (valT == typeof(bool)) return typeof(Boolean);
  780. if (valT == typeof(byte)
  781. || valT == typeof(sbyte)
  782. || valT == typeof(short)
  783. || valT == typeof(ushort)
  784. || valT == typeof(int)
  785. || valT == typeof(uint)
  786. || valT == typeof(long)
  787. || valT == typeof(ulong)) return typeof(Integer);
  788. if (valT == typeof(float)
  789. || valT == typeof(double)
  790. || valT == typeof(decimal)) return typeof(FloatingPoint);
  791. if (typeof(IEnumerable).IsAssignableFrom(valT)) return typeof(List);
  792. // TODO: fill this out the rest of the way
  793. // TODO: support converters
  794. return typeof(Map); // default for various objects
  795. }
  796. private static void EmitDeserializeGeneratedValue(ILGenerator il, Type targetType, Type srcType, Func<Type, int, LocalBuilder> GetLocal)
  797. {
  798. var IGeneratedStore_Deserialize = typeof(IGeneratedStore).GetMethod(nameof(IGeneratedStore.Deserialize));
  799. var valuel = GetLocal(srcType, 0);
  800. il.Emit(OpCodes.Stloc, valuel);
  801. EmitCreateChildGenerated(il, targetType);
  802. il.Emit(OpCodes.Dup);
  803. il.Emit(OpCodes.Ldloc, valuel);
  804. il.Emit(OpCodes.Callvirt, IGeneratedStore_Deserialize);
  805. }
  806. private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, Func<Type, int, LocalBuilder> GetLocal)
  807. {
  808. EmitDeserializeValue(il, member.NullableWrappedType, expected, GetLocal);
  809. il.Emit(OpCodes.Newobj, member.Nullable_Construct);
  810. }
  811. // top of stack is the Value to deserialize; the type will be as returned from GetExpectedValueTypeForType
  812. // after, top of stack will be thing to write to field
  813. private static void EmitDeserializeValue(ILGenerator il, Type targetType, Type expected, Func<Type, int, LocalBuilder> GetLocal)
  814. {
  815. if (typeof(Value).IsAssignableFrom(targetType)) return; // do nothing
  816. if (expected == typeof(Text))
  817. {
  818. var getter = expected.GetProperty(nameof(Text.Value)).GetGetMethod();
  819. il.Emit(OpCodes.Call, getter);
  820. if (targetType == typeof(char))
  821. {
  822. var strIndex = typeof(string).GetProperty("Chars").GetGetMethod(); // string's indexer is specially named Chars
  823. il.Emit(OpCodes.Ldc_I4_0);
  824. il.Emit(OpCodes.Call, strIndex);
  825. }
  826. }
  827. else if (expected == typeof(Boolean))
  828. {
  829. var getter = expected.GetProperty(nameof(Boolean.Value)).GetGetMethod();
  830. il.Emit(OpCodes.Call, getter);
  831. }
  832. else if (expected == typeof(Integer))
  833. {
  834. var getter = expected.GetProperty(nameof(Integer.Value)).GetGetMethod();
  835. il.Emit(OpCodes.Call, getter);
  836. EmitNumberConvertTo(il, targetType, getter.ReturnType);
  837. }
  838. else if (expected == typeof(FloatingPoint))
  839. {
  840. var getter = expected.GetProperty(nameof(FloatingPoint.Value)).GetGetMethod();
  841. il.Emit(OpCodes.Call, getter);
  842. EmitNumberConvertTo(il, targetType, getter.ReturnType);
  843. } // TODO: implement stuff for lists and maps of various types (probably call out somewhere else to figure out what to do)
  844. else if (expected == typeof(Map))
  845. {
  846. EmitDeserializeGeneratedValue(il, targetType, expected, GetLocal);
  847. }
  848. else // TODO: support converters
  849. {
  850. il.Emit(OpCodes.Pop);
  851. il.Emit(OpCodes.Ldnull);
  852. }
  853. }
  854. // emit takes the value being deserialized, logs on error, leaves nothing on stack
  855. private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action<ILGenerator> getValue, Func<Type, int, LocalBuilder> GetLocal)
  856. {
  857. var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType));
  858. var implLabel = il.DefineLabel();
  859. var passedTypeCheck = il.DefineLabel();
  860. var expectType = GetExpectedValueTypeForType(member.IsNullable ? member.NullableWrappedType : member.Type);
  861. void EmitStore(Action<ILGenerator> value)
  862. {
  863. il.Emit(OpCodes.Ldarg_0); // load this
  864. value(il);
  865. if (member.IsField)
  866. il.Emit(OpCodes.Stfld, member.Member as FieldInfo);
  867. else
  868. { // member is a property
  869. var prop = member.Member as PropertyInfo;
  870. var setter = prop.GetSetMethod();
  871. if (setter == null) throw new InvalidOperationException($"Property {member.Name} does not have a setter and is not ignored");
  872. il.Emit(OpCodes.Call, setter);
  873. }
  874. }
  875. il.Emit(OpCodes.Dup);
  876. il.Emit(OpCodes.Brtrue_S, implLabel); // null check
  877. if (!member.AllowNull)
  878. {
  879. il.Emit(OpCodes.Pop);
  880. EmitLogError(il, $"Member {member.Name} ({member.Type}) not nullable", tailcall: false,
  881. expected: il => EmitTypeof(il, expectType));
  882. il.Emit(OpCodes.Br, nextLabel);
  883. }
  884. else if (member.IsNullable)
  885. {
  886. il.Emit(OpCodes.Pop);
  887. var valTLocal = GetLocal(member.Type, 0);
  888. il.Emit(OpCodes.Ldloca, valTLocal);
  889. il.Emit(OpCodes.Initobj, member.Type);
  890. EmitStore(il => il.Emit(OpCodes.Ldloc, valTLocal));
  891. il.Emit(OpCodes.Br, nextLabel);
  892. }
  893. else
  894. {
  895. il.Emit(OpCodes.Pop);
  896. EmitStore(il => il.Emit(OpCodes.Ldnull));
  897. il.Emit(OpCodes.Br, nextLabel);
  898. }
  899. il.MarkLabel(implLabel);
  900. il.Emit(OpCodes.Isinst, expectType); //replaces on stack
  901. il.Emit(OpCodes.Dup); // duplicate cloned value
  902. il.Emit(OpCodes.Brtrue, passedTypeCheck); // null check
  903. var errorHandle = il.DefineLabel();
  904. // special cases to handle coersion between Float and Int
  905. if (expectType == typeof(FloatingPoint))
  906. {
  907. var specialTypeCheck = il.DefineLabel();
  908. il.Emit(OpCodes.Pop);
  909. getValue(il);
  910. il.Emit(OpCodes.Isinst, typeof(Integer)); //replaces on stack
  911. il.Emit(OpCodes.Dup); // duplicate cloned value
  912. il.Emit(OpCodes.Brfalse, errorHandle); // null check
  913. var Integer_CoerceToFloat = typeof(Integer).GetMethod(nameof(Integer.AsFloat));
  914. il.Emit(OpCodes.Call, Integer_CoerceToFloat);
  915. il.Emit(OpCodes.Br, passedTypeCheck);
  916. }
  917. else if (expectType == typeof(Integer))
  918. {
  919. var specialTypeCheck = il.DefineLabel();
  920. il.Emit(OpCodes.Pop);
  921. getValue(il);
  922. il.Emit(OpCodes.Isinst, typeof(FloatingPoint)); //replaces on stack
  923. il.Emit(OpCodes.Dup); // duplicate cloned value
  924. il.Emit(OpCodes.Brfalse, errorHandle); // null check
  925. var Float_CoerceToInt = typeof(FloatingPoint).GetMethod(nameof(FloatingPoint.AsInteger));
  926. il.Emit(OpCodes.Call, Float_CoerceToInt);
  927. il.Emit(OpCodes.Br, passedTypeCheck);
  928. }
  929. il.MarkLabel(errorHandle);
  930. il.Emit(OpCodes.Pop);
  931. EmitLogError(il, $"Unexpected type deserializing {member.Name}", tailcall: false,
  932. expected: il => EmitTypeof(il, expectType), found: il =>
  933. {
  934. getValue(il);
  935. il.Emit(OpCodes.Callvirt, Object_GetType);
  936. });
  937. il.Emit(OpCodes.Br, nextLabel);
  938. il.MarkLabel(passedTypeCheck);
  939. var local = GetLocal(member.Type, 0);
  940. if (member.IsNullable) EmitDeserializeNullable(il, member, expectType, GetLocal);
  941. else EmitDeserializeValue(il, member.Type, expectType, GetLocal);
  942. il.Emit(OpCodes.Stloc, local);
  943. EmitStore(il => il.Emit(OpCodes.Ldloc, local));
  944. }
  945. #endregion
  946. }
  947. }