using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager.Definition; using Robust.Shared.Serialization.Manager.Exceptions; using Robust.Shared.Serialization.Markdown; using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Serialization.Markdown.Sequence; using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Interfaces; using Robust.Shared.Utility; // Avoid accidentally mixing up overloads. // ReSharper disable RedundantTypeArgumentsOfMethod namespace Robust.Shared.Serialization.Manager { public partial class SerializationManager { private delegate object? ReadBoxingDelegate( DataNode node, SerializationHookContext hookCtx, ISerializationContext? context = null); private delegate T ReadGenericDelegate( DataNode node, SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate? instanceProvider = null); private delegate object? ReadDelegate( DataNode node, SerializationHookContext hookCtx, ISerializationContext? context = null, bool notNullableOverride = false ); private MethodInfo _read = default!; private readonly ConcurrentDictionary<(Type type, bool notNullableOverride), ReadBoxingDelegate> _readBoxingDelegates = new(); private readonly ConcurrentDictionary<(Type baseType, Type actualType, Type node, bool notNullableOverride), object> _readGenericBaseDelegates = new(); private readonly ConcurrentDictionary<(Type value, Type node, bool notNullableOverride), object> _readGenericDelegates = new(); private readonly ConcurrentDictionary> _reads = new(); public T Read(DataNode node, ISerializationContext? context = null, bool skipHook = false, ISerializationManager.InstantiationDelegate? instanceProvider = null, bool notNullableOverride = false) { return Read( node, SerializationHookContext.ForSkipHooks(skipHook), context, instanceProvider, notNullableOverride); } public T[] ReadArray( DataNode node, SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate? instanceProvider = null, bool notNullableOverride = false) { var baseType = typeof(T); var nullable = baseType.IsNullable(); var isValueType = baseType.IsValueType; T[]? val = null; if (instanceProvider != null) val ??= []; if (context != null) { switch (node) { case MappingDataNode mapping: { if (context.SerializerProvider .TryGetTypeNodeSerializerArray, T, MappingDataNode>( out var serializer)) { val = serializer.Read(this, mapping, DependencyCollection, hookCtx, context, instanceProvider); if (notNullableOverride) Debug.Assert(val != null, "Reader call returned null value! Forbidden!"); } else { RegularRead(); } break; } case SequenceDataNode sequence: { if (context.SerializerProvider .TryGetTypeNodeSerializerArray, T, SequenceDataNode>( out var serializer)) { val = serializer.Read(this, sequence, DependencyCollection, hookCtx, context, instanceProvider); if (notNullableOverride) Debug.Assert(val != null, "Reader call returned null value! Forbidden!"); } else { RegularRead(); } break; } case ValueDataNode value: { if (context.SerializerProvider .TryGetTypeNodeSerializerArray, T, ValueDataNode>( out var serializer)) { val = serializer.Read(this, value, DependencyCollection, hookCtx, context, instanceProvider); if (notNullableOverride) Debug.Assert(val != null, "Reader call returned null value! Forbidden!"); } else { RegularRead(); } break; } } } else { RegularRead(); } if (node.IsNull) { if (nullable && !notNullableOverride) val = null; else throw new NullNotAllowedException(); } if (!nullable && !isValueType && val == null) throw new ReadCallReturnedNullException(); return val!; void RegularRead() { switch (node) { case MappingDataNode mapping: { if (_regularSerializerProvider.TryGetTypeNodeSerializerArray, T, MappingDataNode>(out var reader)) { val = Read(reader, mapping, hookCtx, context, instanceProvider, notNullableOverride); } break; } case SequenceDataNode sequence: { if (_regularSerializerProvider.TryGetTypeNodeSerializerArray, T, SequenceDataNode>(out var reader)) { val = Read(reader, sequence, hookCtx, context, instanceProvider, notNullableOverride); } break; } case ValueDataNode value: { if (_regularSerializerProvider.TryGetTypeNodeSerializerArray, T, ValueDataNode>(out var reader)) { val = Read(reader, value, hookCtx, context, instanceProvider, notNullableOverride); } break; } } val = node switch { SequenceDataNode sequence => ReadArraySequence(sequence, hookCtx, context), ValueDataNode value => ReadArrayValue(value, hookCtx, context), _ => throw new ArgumentException($"Cannot read array from data node type {node.GetType()}") }; } } public T ReadStructDefinition( DataNode node, SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate? instanceProvider = null, bool notNullableOverride = false) where T : struct, ISerializationGenerated { var baseType = typeof(T); var nullable = baseType.IsNullable(); var isArray = ReadTypeMetadata.IsArray; T val = default!; if (instanceProvider != null) val = instanceProvider.Invoke(); if (context != null) { switch (node) { case MappingDataNode mapping: { if (context.SerializerProvider .TryGetTypeNodeSerializer, T, MappingDataNode>( out var serializer)) { val = Read( serializer, mapping, hookCtx, context, instanceProvider, notNullableOverride ); } else { RegularRead(); } break; } case SequenceDataNode sequence: { if (context.SerializerProvider .TryGetTypeNodeSerializer, T, SequenceDataNode>( out var serializer)) { val = Read( serializer, sequence, hookCtx, context, instanceProvider, notNullableOverride ); } else { RegularRead(); } break; } case ValueDataNode value: { if (context.SerializerProvider .TryGetTypeNodeSerializer, T, ValueDataNode>( out var serializer)) { val = Read( serializer, value, hookCtx, context, instanceProvider, notNullableOverride ); } else { RegularRead(); } break; } } } else { RegularRead(); } if (node.IsNull) { if (nullable && !notNullableOverride) val = default!; else if (baseType == typeof(EntityUid)) val = default!; else throw new NullNotAllowedException(); } return val; void RegularRead() { var hasSerializer = false; switch (node) { case MappingDataNode mapping: { if (_regularSerializerProvider.TryGetTypeNodeSerializer, T, MappingDataNode>(out var reader)) { hasSerializer = true; val = Read(reader, mapping, hookCtx, context, instanceProvider, notNullableOverride); } break; } case SequenceDataNode sequence: { if (_regularSerializerProvider.TryGetTypeNodeSerializer, T, SequenceDataNode>(out var reader)) { hasSerializer = true; val = Read(reader, sequence, hookCtx, context, instanceProvider, notNullableOverride); } break; } case ValueDataNode value: { if (_regularSerializerProvider.TryGetTypeNodeSerializer, T, ValueDataNode>(out var reader)) { hasSerializer = true; val = Read(reader, value, hookCtx, context, instanceProvider, notNullableOverride); } break; } } if (!hasSerializer) { if (isArray) { val = node switch { SequenceDataNode sequence => (T)(object)ReadArraySequence(sequence, hookCtx, context), ValueDataNode value => (T)(object)ReadArrayValue(value, hookCtx, context), _ => throw new ArgumentException($"Cannot read array from data node type {node.GetType()}") }; } else if (baseType.IsEnum) { val = node switch { SequenceDataNode sequence => ReadEnumSequence(sequence), ValueDataNode value => ReadEnumValue(value), _ => throw new InvalidNodeTypeException( $"Cannot serialize node as {baseType}, unsupported node type {node.GetType()}") }; } else if (baseType.IsAssignableTo(typeof(ISelfSerialize))) { switch (node) { case ValueDataNode value: { instanceProvider ??= T.StaticInstantiate; val = instanceProvider(); var selfSerialize = (ISelfSerialize) val; selfSerialize.Deserialize(value.Value); val = (T) selfSerialize; break; } default: throw new InvalidNodeTypeException($"Cannot read {nameof(ISelfSerialize)} from node type {node.GetType()}. Expected {nameof(ValueDataNode)}"); } } else { switch (node) { case MappingDataNode mapping: { instanceProvider ??= T.StaticInstantiate; val = instanceProvider(); T.Read(ref val, mapping, this, hookCtx, context); break; } case ValueDataNode value: { instanceProvider ??= T.StaticInstantiate; val = instanceProvider(); if (value.Value != string.Empty) throw new ArgumentException($"No mapping node provided for type {typeof(T)} at line: {node.Start.Line}"); break; } default: throw new ArgumentException($"No mapping or value node provided for type {baseType}."); } TryRunAfterHook(val, hookCtx); } } } } public T ReadEnum( DataNode node, SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate? instanceProvider = null, bool notNullableOverride = false) where T : struct, Enum { var baseType = typeof(T); var nullable = baseType.IsNullable(); T val = default!; if (instanceProvider != null) val = instanceProvider.Invoke(); if (context != null) { switch (node) { case MappingDataNode mapping: { if (context.SerializerProvider .TryGetTypeNodeSerializer, T, MappingDataNode>( out var serializer)) { val = Read( serializer, mapping, hookCtx, context, instanceProvider, notNullableOverride ); } else { RegularRead(); } break; } case SequenceDataNode sequence: { if (context.SerializerProvider .TryGetTypeNodeSerializer, T, SequenceDataNode>( out var serializer)) { val = Read( serializer, sequence, hookCtx, context, instanceProvider, notNullableOverride ); } else { RegularRead(); } break; } case ValueDataNode value: { if (context.SerializerProvider .TryGetTypeNodeSerializer, T, ValueDataNode>( out var serializer)) { val = Read( serializer, value, hookCtx, context, instanceProvider, notNullableOverride ); } else { RegularRead(); } break; } } } else { RegularRead(); } if (node.IsNull) { if (nullable && !notNullableOverride) val = default!; else throw new NullNotAllowedException(); } return val; void RegularRead() { var hasSerializer = false; switch (node) { case MappingDataNode mapping: { if (_regularSerializerProvider.TryGetTypeNodeSerializer, T, MappingDataNode>(out var reader)) { hasSerializer = true; val = Read(reader, mapping, hookCtx, context, instanceProvider, notNullableOverride); } break; } case SequenceDataNode sequence: { if (_regularSerializerProvider.TryGetTypeNodeSerializer, T, SequenceDataNode>(out var reader)) { hasSerializer = true; val = Read(reader, sequence, hookCtx, context, instanceProvider, notNullableOverride); } break; } case ValueDataNode value: { if (_regularSerializerProvider.TryGetTypeNodeSerializer, T, ValueDataNode>(out var reader)) { hasSerializer = true; val = Read(reader, value, hookCtx, context, instanceProvider, notNullableOverride); } break; } } if (hasSerializer) return; val = node switch { SequenceDataNode sequence => ReadEnumSequence(sequence), ValueDataNode value => ReadEnumValue(value), _ => throw new InvalidNodeTypeException( $"Cannot serialize node as {baseType}, unsupported node type {node.GetType()}") }; } } private object? ReadObject( DataNode node, SerializationHookContext hookCtx, ISerializationContext? context = null, bool notNullableOverride = false) { return Read(node, hookCtx, context, null, notNullableOverride); } public T Read( DataNode node, SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate? instanceProvider = null, bool notNullableOverride = false) { if (node.Tag?.StartsWith("!type:") ?? false) { var type = ResolveConcreteType(typeof(T), node.Tag.Substring(6)); if (type.IsInterface || type.IsAbstract) { throw new ArgumentException($"Interface or abstract type used for !type node. Type: {type}"); } //!type tag overrides null value on default. i did this because i couldnt come up with a usecase where you'd specify the type but have a null value. yell at me if you found one -paul if (node.IsEmpty || node.IsNull) { if (instanceProvider != null) { var instantiatedVal = instanceProvider(); //make this debug-only? -(false, type)(); } return ((ReadGenericDelegate)_readGenericBaseDelegates.GetOrAdd( (typeof(T), type, node.GetType()!, notNullableOverride), static (tuple, manager) => ReadDelegateValueFactory(tuple.baseType, tuple.actualType, tuple.node, tuple.notNullableOverride, manager), this))(node, hookCtx, context, instanceProvider); } var baseType = typeof(T); if (baseType.IsEnum || ReadTypeMetadata.IsArray || (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == typeof(Nullable<>))) { return ((ReadGenericDelegate)_readGenericDelegates.GetOrAdd((typeof(T), node.GetType()!, notNullableOverride), static (tuple, manager) => ReadDelegateValueFactory(tuple.value, tuple.value, tuple.node, tuple.notNullableOverride, manager), this))(node, hookCtx, context, instanceProvider); } var nullable = baseType.IsNullable(); T val = default!; if (node.IsNull) { if (nullable && !notNullableOverride) return default!; if (baseType == typeof(EntityUid)) return (T) (object) EntityUid.Invalid; throw new NullNotAllowedException(); } if (instanceProvider != null) val = instanceProvider.Invoke(); if (context != null) { switch (node) { // TODO actual type for type tag case MappingDataNode mapping: { if (context.SerializerProvider .TryGetTypeNodeSerializer, T, MappingDataNode>( out var serializer)) { val = Read( serializer, mapping, hookCtx, context, instanceProvider, notNullableOverride ); } else { RegularRead(); } break; } case SequenceDataNode sequence: { if (context.SerializerProvider .TryGetTypeNodeSerializer, T, SequenceDataNode>( out var serializer)) { val = Read( serializer, sequence, hookCtx, context, instanceProvider, notNullableOverride ); } else { RegularRead(); } break; } case ValueDataNode value: { if (context.SerializerProvider .TryGetTypeNodeSerializer, T, ValueDataNode>( out var serializer)) { val = Read( serializer, value, hookCtx, context, instanceProvider, notNullableOverride ); } else { RegularRead(); } break; } } } else { RegularRead(); } var isValueType = baseType.IsValueType; if (!nullable && !isValueType && val == null) throw new ReadCallReturnedNullException(); return val; void RegularRead() { var hasSerializer = false; switch (node) { case MappingDataNode mapping: { if (_regularSerializerProvider.TryGetTypeNodeSerializer, T, MappingDataNode>(out var reader)) { hasSerializer = true; val = Read(reader, mapping, hookCtx, context, instanceProvider, notNullableOverride); } break; } case SequenceDataNode sequence: { if (_regularSerializerProvider.TryGetTypeNodeSerializer, T, SequenceDataNode>(out var reader)) { hasSerializer = true; val = Read(reader, sequence, hookCtx, context, instanceProvider, notNullableOverride); } break; } case ValueDataNode value: { if (_regularSerializerProvider.TryGetTypeNodeSerializer, T, ValueDataNode>(out var reader)) { hasSerializer = true; val = Read(reader, value, hookCtx, context, instanceProvider, notNullableOverride); } break; } } if (!hasSerializer) { if (baseType.IsAssignableTo(typeof(ISelfSerialize))) { switch (node) { case ValueDataNode value: { instanceProvider ??= GetOrCreateInstantiator(false); val = instanceProvider(); var selfSerialize = (ISelfSerialize?) val; selfSerialize!.Deserialize(value.Value); val = (T) selfSerialize; break; } default: throw new InvalidNodeTypeException($"Cannot read {nameof(ISelfSerialize)} from node type {node.GetType()}. Expected {nameof(ValueDataNode)}"); } } else { switch (node) { case MappingDataNode mapping: { var definition = GetDefinition(typeof(T)); if (definition == null) throw new ArgumentException($"No data definition found for type {baseType} with node type {node.GetType()} when reading"); var valObj = instanceProvider == null ? definition.InstantiateObj() : instanceProvider.Invoke()!; definition.PopulateObj(ref valObj, mapping, this, hookCtx, context); val = (T) valObj; break; } case ValueDataNode value: { instanceProvider ??= GetOrCreateInstantiator(false); val = instanceProvider(); if (value.Value != string.Empty) throw new ArgumentException($"No mapping node provided for type {baseType} at line: {node.Start.Line}"); break; } default: throw new ArgumentException($"No mapping or value node provided for type {baseType}."); } TryRunAfterHook(val, hookCtx); } } } } public T ReadDefinition( DataNode node, SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate? instanceProvider = null, bool notNullableOverride = false) where T : ISerializationGenerated { if (node.Tag?.StartsWith("!type:") ?? false) { var type = ResolveConcreteType(typeof(T), node.Tag.Substring(6)); if (type.IsInterface || type.IsAbstract) { throw new ArgumentException($"Interface or abstract type used for !type node. Type: {type}"); } //!type tag overrides null value on default. i did this because i couldnt come up with a usecase where you'd specify the type but have a null value. yell at me if you found one -paul if (node.IsEmpty || node.IsNull) { if (instanceProvider != null) { var instantiatedVal = instanceProvider(); //make this debug-only? -(false, type)(); } return ((ReadGenericDelegate)_readGenericBaseDelegates.GetOrAdd( (typeof(T), type, node.GetType()!, notNullableOverride), static (tuple, manager) => ReadDelegateValueFactory(tuple.baseType, tuple.actualType, tuple.node, tuple.notNullableOverride, manager), this))(node, hookCtx, context, instanceProvider); } var baseType = typeof(T); var nullable = baseType.IsNullable(); T val = default!; if (node.IsNull) { if (nullable && !notNullableOverride) return default!; if (baseType == typeof(EntityUid)) return (T) (object) EntityUid.Invalid; throw new NullNotAllowedException(); } if (instanceProvider != null) val = instanceProvider.Invoke(); if (context != null) { switch (node) { case MappingDataNode mapping: { if (context.SerializerProvider .TryGetTypeNodeSerializer, T, MappingDataNode>( out var serializer)) { val = Read( serializer, mapping, hookCtx, context, instanceProvider, notNullableOverride ); } else { RegularRead(); } break; } case SequenceDataNode sequence: { if (context.SerializerProvider .TryGetTypeNodeSerializer, T, SequenceDataNode>( out var serializer)) { val = Read( serializer, sequence, hookCtx, context, instanceProvider, notNullableOverride ); } else { RegularRead(); } break; } case ValueDataNode value: { if (context.SerializerProvider .TryGetTypeNodeSerializer, T, ValueDataNode>( out var serializer)) { val = Read( serializer, value, hookCtx, context, instanceProvider, notNullableOverride ); } else { RegularRead(); } break; } } } else { RegularRead(); } var isValueType = baseType.IsValueType; if (!nullable && !isValueType && val == null) throw new ReadCallReturnedNullException(); return val; void RegularRead() { var hasSerializer = false; switch (node) { case MappingDataNode mapping: { if (_regularSerializerProvider.TryGetTypeNodeSerializer, T, MappingDataNode>(out var reader)) { hasSerializer = true; val = Read(reader, mapping, hookCtx, context, instanceProvider, notNullableOverride); } break; } case SequenceDataNode sequence: { if (_regularSerializerProvider.TryGetTypeNodeSerializer, T, SequenceDataNode>(out var reader)) { hasSerializer = true; val = Read(reader, sequence, hookCtx, context, instanceProvider, notNullableOverride); } break; } case ValueDataNode value: { if (_regularSerializerProvider.TryGetTypeNodeSerializer, T, ValueDataNode>(out var reader)) { hasSerializer = true; val = Read(reader, value, hookCtx, context, instanceProvider, notNullableOverride); } break; } } if (!hasSerializer) { if (baseType.IsAssignableTo(typeof(ISelfSerialize))) { switch (node) { case ValueDataNode value: { instanceProvider ??= T.StaticInstantiate; val = instanceProvider(); var selfSerialize = (ISelfSerialize) val; selfSerialize.Deserialize(value.Value); val = (T) selfSerialize; break; } default: throw new InvalidNodeTypeException($"Cannot read {nameof(ISelfSerialize)} from node type {node.GetType()}. Expected {nameof(ValueDataNode)}"); } } else { switch (node) { case MappingDataNode mapping: { instanceProvider ??= T.StaticInstantiate; val = instanceProvider(); T.Read(ref val, mapping, this, hookCtx, context); break; } case ValueDataNode value: { instanceProvider ??= T.StaticInstantiate; val = instanceProvider(); if (value.Value != string.Empty) throw new ArgumentException($"No mapping node provided for type {baseType} at line: {node.Start.Line}"); break; } default: throw new ArgumentException($"No mapping or value node provided for type {baseType}."); } TryRunAfterHook(val, hookCtx); } } } } public T Read( ITypeReader reader, TNode node, ISerializationContext? context = null, bool skipHook = false, ISerializationManager.InstantiationDelegate? instanceProvider = null, bool notNullableOverride = false) where TNode : DataNode { return Read( reader, node, SerializationHookContext.ForSkipHooks(skipHook), context, instanceProvider, notNullableOverride); } public T Read( ITypeReader reader, TNode node, SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate? instanceProvider = null, bool notNullableOverride = false) where TNode : DataNode { var val = reader.Read(this, node, DependencyCollection, hookCtx, context, instanceProvider); if (notNullableOverride) Debug.Assert(val != null, "Reader call returned null value! Forbidden!"); return val; } public T Read(TNode node, ISerializationContext? context = null, bool skipHook = false, ISerializationManager.InstantiationDelegate? instanceProvider = null, bool notNullableOverride = false) where TNode : DataNode where TReader : ITypeReader { return Read( node, SerializationHookContext.ForSkipHooks(skipHook), context, instanceProvider, notNullableOverride); } public T Read( TNode node, SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate? instanceProvider = null, bool notNullableOverride = false) where TNode : DataNode where TReader : ITypeReader { return Read( GetOrCreateCustomTypeSerializer(), node, hookCtx, context, instanceProvider, notNullableOverride); } public object? Read(Type type, DataNode node, ISerializationContext? context = null, bool skipHook = false, bool notNullableOverride = false) { return Read(type, node, SerializationHookContext.ForSkipHooks(skipHook), context, notNullableOverride); } public object? Read( Type type, DataNode node, SerializationHookContext hookCtx, ISerializationContext? context = null, bool notNullableOverride = false) { return GetOrCreateBoxingReadDelegate(type, notNullableOverride)(node, hookCtx, context); } private ReadBoxingDelegate GetOrCreateBoxingReadDelegate(Type type, bool notNullableOverride = false) { return _readBoxingDelegates.GetOrAdd((type, notNullableOverride), static (tuple, manager) => { var type = tuple.type; var managerConst = Expression.Constant(manager); var nodeParam = Expression.Variable(typeof(DataNode)); var contextParam = Expression.Variable(typeof(ISerializationContext)); var hookCtxParam = Expression.Variable(typeof(SerializationHookContext)); var call = Expression.Convert(Expression.Call( managerConst, nameof(Read), new[] { type }, nodeParam, hookCtxParam, contextParam, Expression.Constant(null, typeof(ISerializationManager.InstantiationDelegate<>).MakeGenericType(type)), Expression.Constant(tuple.notNullableOverride)), typeof(object)); return Expression.Lambda( call, nodeParam, hookCtxParam, contextParam).Compile(); }, this); } private static object ReadDelegateValueFactory(Type baseType, Type actualType, Type nodeType, bool notNullableOverride, SerializationManager manager) { var nullable = actualType.IsNullable(); var managerConst = Expression.Constant(manager); var nodeParam = Expression.Parameter(typeof(DataNode), "node"); var contextParam = Expression.Parameter(typeof(ISerializationContext), "context"); var hookCtxParam = Expression.Parameter(typeof(SerializationHookContext), "hookCtx"); var instantiatorParam = Expression.Parameter(typeof(ISerializationManager.InstantiationDelegate<>).MakeGenericType(baseType), "instanceProvider"); actualType = actualType.EnsureNotNullableType(); Expression BaseInstantiatorToActual() { Expression nonNullableInstantiator = baseType.IsNullable() && baseType.IsValueType ? Expression.Call( managerConst, nameof(UnwrapInstantiationDelegate), new[] { baseType.EnsureNotNullableType() }, instantiatorParam) : instantiatorParam; return baseType.EnsureNotNullableType() == actualType ? nonNullableInstantiator : Expression.Call(managerConst, nameof(WrapBaseInstantiationDelegate), new []{actualType, baseType.EnsureNotNullableType()}, instantiatorParam); } var instantiatorVariable = Expression.Variable(typeof(ISerializationManager.InstantiationDelegate<>).MakeGenericType(actualType)); var instantiatorCoalesce = Expression.Assign(instantiatorVariable, Expression.Coalesce( BaseInstantiatorToActual(), Expression.Call( managerConst, nameof(GetOrCreateInstantiator), new[] { actualType }, Expression.Constant(false), Expression.Constant(null, typeof(Type))))); Expression call; if (manager._regularSerializerProvider.TryGetTypeNodeSerializer(typeof(ITypeReader<,>), actualType, nodeType, out var reader)) { var readerType = typeof(ITypeReader<,>).MakeGenericType(actualType, nodeType); var readerConst = Expression.Constant(reader, readerType); call = Expression.Call( managerConst, nameof(Read), new[] { actualType, nodeType }, readerConst, Expression.Convert(nodeParam, nodeType), hookCtxParam, contextParam, BaseInstantiatorToActual(), Expression.Constant(notNullableOverride)); } else if (actualType.IsArray) { var elementType = actualType.GetElementType()!; if (nodeType == typeof(ValueDataNode)) { call = Expression.Call( managerConst, nameof(ReadArrayValue), new[] { elementType }, Expression.Convert(nodeParam, typeof(ValueDataNode)), hookCtxParam, contextParam); } else if (nodeType == typeof(SequenceDataNode)) { call = Expression.Call( managerConst, nameof(ReadArraySequence), new[] { elementType }, Expression.Convert(nodeParam, typeof(SequenceDataNode)), hookCtxParam, contextParam); } else { throw new ArgumentException($"Cannot read array from data node type {nodeType}"); } } else if (actualType.IsEnum) { // Does not include cases where the target type is System.Enum. // Those get handled by the generic enum serializer which uses reflection to resolve strings into enums. DebugTools.Assert(actualType != typeof(Enum)); if (nodeType == typeof(ValueDataNode)) { call = Expression.Call(managerConst, nameof(ReadEnumValue), new[] { actualType }, Expression.Convert(nodeParam, typeof(ValueDataNode))); } else if (nodeType == typeof(SequenceDataNode)) { call = Expression.Call(managerConst, nameof(ReadEnumSequence), new[] { actualType }, Expression.Convert(nodeParam, typeof(SequenceDataNode))); } else { throw new InvalidNodeTypeException($"Cannot serialize node as {actualType}, unsupported node type {nodeType}"); } } else if (actualType.IsAssignableTo(typeof(ISelfSerialize))) { if (nodeType != typeof(ValueDataNode)) { throw new InvalidNodeTypeException($"Cannot read {nameof(ISelfSerialize)} from node type {nodeType}. Expected {nameof(ValueDataNode)}"); } call = Expression.Block( new [] {instantiatorVariable}, instantiatorCoalesce, Expression.Call( managerConst, nameof(ReadSelfSerialize), new[] { actualType }, instantiatorVariable, Expression.Convert(nodeParam, typeof(ValueDataNode)))); } else { if (nodeType == typeof(ValueDataNode)) { call = Expression.Call( managerConst, nameof(ReadGenericValue), new[] { actualType }, Expression.Convert(nodeParam, typeof(ValueDataNode)), hookCtxParam, instantiatorVariable); } else if (nodeType == typeof(MappingDataNode)) { if (actualType.IsAssignableTo(typeof(ISerializationGenerated<>).MakeGenericType(actualType))) { var definition = manager.GetDefinition(actualType); var definitionConst = Expression.Constant(definition, typeof(DataDefinition<>).MakeGenericType(actualType)); call = Expression.Call( managerConst, nameof(ReadGenericMapping), new[] { actualType }, Expression.Convert(nodeParam, typeof(MappingDataNode)), definitionConst, hookCtxParam, contextParam, instantiatorVariable); } else { call = Expression.Call( managerConst, nameof(ReadNoSerializer), new[] { actualType }, nodeParam); } } else { throw new ArgumentException($"No mapping or value node provided for type {actualType}."); } call = Expression.Block( new[]{instantiatorVariable}, instantiatorCoalesce, call); } // check for customtypeserializer var serializerType = typeof(ITypeReader<,>).MakeGenericType(actualType, nodeType); var serializerVar = Expression.Variable(serializerType); call = Expression.Block(new[] { serializerVar }, Expression.Condition( Expression.AndAlso( Expression.ReferenceNotEqual(contextParam, Expression.Constant(null, typeof(ISerializationContext))), Expression.Call(Expression.Property(contextParam, "SerializerProvider"), "TryGetTypeNodeSerializer", new[] { serializerType, actualType, nodeType }, serializerVar)), Expression.Call( managerConst, nameof(Read), new []{actualType, nodeType}, serializerVar, Expression.Convert(nodeParam, nodeType), hookCtxParam, contextParam, BaseInstantiatorToActual(), Expression.Constant(notNullableOverride)), call)); //wrap our valuetype in nullable if we are nullable so we can assign it to returnValue call = WrapNullableIfNeededExpression(call, nullable); // early-out null before anything var returnValue = Expression.Variable(nullable ? actualType.EnsureNullableType() : actualType); call = Expression.Block(new[] { returnValue }, Expression.IfThenElse( Expression.Call(typeof(SerializationManager), nameof(IsNull), Type.EmptyTypes, nodeParam), nullable && !notNullableOverride ? Expression.Block(typeof(void), Expression.Assign(returnValue, GetNullExpression(managerConst, actualType))) : actualType == typeof(EntityUid) //todo paul make this not hardcoded ? Expression.Assign(returnValue, Expression.Constant(EntityUid.Invalid)) : ExpressionUtils.ThrowExpression(), Expression.Block(typeof(void), Expression.Assign(returnValue, call))), returnValue); if (!nullable && !actualType.IsValueType) { // check that value isn't null var finalValue = Expression.Variable(baseType); call = Expression.Block(new[] { finalValue }, Expression.Assign(finalValue, call), Expression.IfThen(Expression.Equal(finalValue, GetNullExpression(managerConst, actualType)), ExpressionUtils.ThrowExpression()), finalValue); } return Expression.Lambda( typeof(ReadGenericDelegate<>).MakeGenericType(baseType), call, nodeParam, hookCtxParam, contextParam, instantiatorParam) .Compile(); } private ISerializationManager.InstantiationDelegate? UnwrapInstantiationDelegate( ISerializationManager.InstantiationDelegate? instantiationDelegate) where T : struct { if (instantiationDelegate == null) return null; return () => { var val = instantiationDelegate(); Debug.Assert(val.HasValue, $"{nameof(instantiationDelegate)} returned null value! This should NEVER be allowed to happen!"); return instantiationDelegate()!.Value; }; } private ISerializationManager.InstantiationDelegate? WrapBaseInstantiationDelegate( ISerializationManager.InstantiationDelegate? instantiationDelegate) where TActual : TBase { if (instantiationDelegate == null) return null; return () => { var val = instantiationDelegate(); Debug.Assert(val != null, $"{nameof(instantiationDelegate)} returned null value! This should NEVER be allowed to happen!"); return (TActual)val; }; } private T[] ReadArrayValue( ValueDataNode value, SerializationHookContext hookCtx, ISerializationContext? context = null) { var array = new T[1]; array[0] = Read(value, hookCtx, context); return array; } private T[] ReadArraySequence( SequenceDataNode node, SerializationHookContext hookCtx, ISerializationContext? context = null) { var array = new T[node.Sequence.Count]; for (var i = 0; i < node.Sequence.Count; i++) { array[i] = Read(node.Sequence[i], hookCtx, context); } return array; } private TEnum ReadEnumValue(ValueDataNode node) where TEnum : struct { return Enum.Parse(node.Value, true); } private TEnum ReadEnumSequence(SequenceDataNode node) where TEnum : struct { return Enum.Parse(string.Join(", ", node.Sequence), true); } private TValue ReadSelfSerialize( ISerializationManager.InstantiationDelegate instanceProvider, ValueDataNode node) where TValue : ISelfSerialize { var val = instanceProvider(); val.Deserialize(node.Value); return val; } private TValue ReadGenericValue( ValueDataNode node, SerializationHookContext hookCtx, ISerializationManager.InstantiationDelegate instanceProvider) where TValue : notnull { var type = typeof(TValue); var instance = instanceProvider(); if (node.Value != string.Empty) { throw new ArgumentException($"No mapping node provided for type {type} at line: {node.Start.Line}"); } TryRunAfterHook(instance, hookCtx); return instance; } private TValue ReadGenericMapping( MappingDataNode node, DataDefinition? definition, SerializationHookContext hookCtx, ISerializationContext? context, ISerializationManager.InstantiationDelegate instanceProvider) where TValue : ISerializationGenerated { if (definition == null) throw new ArgumentException($"No data definition found for type {typeof(TValue)} with node type {node.GetType()} when reading"); var instance = instanceProvider(); definition.Populate(ref instance, node, this, hookCtx, context); TryRunAfterHook(instance, hookCtx); return instance; } private TValue ReadNoSerializer(DataNode node) { throw new ArgumentException($"No type serializer or data definition found for type {typeof(TValue)} with node type {node.GetType()} when reading"); } private static class ReadTypeMetadata { // ReSharper disable once StaticMemberInGenericType public static bool IsArray; static ReadTypeMetadata() { IsArray = typeof(T).IsArray; } } } }