using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using ProtoBuf.Compiler; using ProtoBuf.Meta; namespace ProtoBuf.Serializers; internal sealed class ImmutableCollectionDecorator : ListDecorator { private readonly MethodInfo builderFactory; private readonly MethodInfo add; private readonly MethodInfo addRange; private readonly MethodInfo finish; private readonly PropertyInfo isEmpty; private readonly PropertyInfo length; protected override bool RequireAdd => false; private static Type ResolveIReadOnlyCollection(Type declaredType, Type t) { if (CheckIsIReadOnlyCollectionExactly(declaredType)) { return declaredType; } Type[] interfaces = declaredType.GetInterfaces(); foreach (Type type in interfaces) { if (CheckIsIReadOnlyCollectionExactly(type)) { return type; } } return null; } private static bool CheckIsIReadOnlyCollectionExactly(Type t) { if ((object)t != null && t.IsGenericType && t.Name.StartsWith("IReadOnlyCollection`")) { Type[] genericArguments = t.GetGenericArguments(); if (genericArguments.Length != 1 && (object)genericArguments[0] != t) { return false; } return true; } return false; } internal static bool IdentifyImmutable(TypeModel model, Type declaredType, out MethodInfo builderFactory, out PropertyInfo isEmpty, out PropertyInfo length, out MethodInfo add, out MethodInfo addRange, out MethodInfo finish) { builderFactory = (add = (addRange = (finish = null))); isEmpty = (length = null); if (model == null || (object)declaredType == null) { return false; } if (!declaredType.IsGenericType) { return false; } Type[] genericArguments = declaredType.GetGenericArguments(); Type[] array; switch (genericArguments.Length) { case 1: array = genericArguments; break; case 2: { Type type = model.MapType(typeof(KeyValuePair<, >)); if ((object)type == null) { return false; } type = type.MakeGenericType(genericArguments); array = new Type[1] { type }; break; } default: return false; } if ((object)ResolveIReadOnlyCollection(declaredType, null) == null) { return false; } string name = declaredType.Name; int num = name.IndexOf('`'); if (num <= 0) { return false; } name = (declaredType.IsInterface ? name.Substring(1, num - 1) : name.Substring(0, num)); Type type2 = model.GetType(declaredType.Namespace + "." + name, declaredType.Assembly); if ((object)type2 == null && name == "ImmutableSet") { type2 = model.GetType(declaredType.Namespace + ".ImmutableHashSet", declaredType.Assembly); } if ((object)type2 == null) { return false; } MethodInfo[] methods = type2.GetMethods(); foreach (MethodInfo methodInfo in methods) { if (methodInfo.IsStatic && !(methodInfo.Name != "CreateBuilder") && methodInfo.IsGenericMethodDefinition && methodInfo.GetParameters().Length == 0 && methodInfo.GetGenericArguments().Length == genericArguments.Length) { builderFactory = methodInfo.MakeGenericMethod(genericArguments); break; } } Type type3 = model.MapType(typeof(void)); if ((object)builderFactory == null || (object)builderFactory.ReturnType == null || (object)builderFactory.ReturnType == type3) { return false; } isEmpty = Helpers.GetProperty(declaredType, "IsDefaultOrEmpty", nonPublic: false); if ((object)isEmpty == null) { isEmpty = Helpers.GetProperty(declaredType, "IsEmpty", nonPublic: false); } if ((object)isEmpty == null) { length = Helpers.GetProperty(declaredType, "Length", nonPublic: false); if ((object)length == null) { length = Helpers.GetProperty(declaredType, "Count", nonPublic: false); } if ((object)length == null) { length = Helpers.GetProperty(ResolveIReadOnlyCollection(declaredType, array[0]), "Count", nonPublic: false); } if ((object)length == null) { return false; } } add = Helpers.GetInstanceMethod(builderFactory.ReturnType, "Add", array); if ((object)add == null) { return false; } finish = Helpers.GetInstanceMethod(builderFactory.ReturnType, "ToImmutable", Helpers.EmptyTypes); if ((object)finish == null || (object)finish.ReturnType == null || (object)finish.ReturnType == type3) { return false; } if ((object)finish.ReturnType != declaredType && !Helpers.IsAssignableFrom(declaredType, finish.ReturnType)) { return false; } addRange = Helpers.GetInstanceMethod(builderFactory.ReturnType, "AddRange", new Type[1] { declaredType }); if ((object)addRange == null) { Type type4 = model.MapType(typeof(IEnumerable<>), demand: false); if ((object)type4 != null) { addRange = Helpers.GetInstanceMethod(builderFactory.ReturnType, "AddRange", new Type[1] { type4.MakeGenericType(array) }); } } return true; } internal ImmutableCollectionDecorator(TypeModel model, Type declaredType, Type concreteType, IProtoSerializer tail, int fieldNumber, bool writePacked, WireType packedWireType, bool returnList, bool overwriteList, bool supportNull, MethodInfo builderFactory, PropertyInfo isEmpty, PropertyInfo length, MethodInfo add, MethodInfo addRange, MethodInfo finish) : base(model, declaredType, concreteType, tail, fieldNumber, writePacked, packedWireType, returnList, overwriteList, supportNull) { this.builderFactory = builderFactory; this.isEmpty = isEmpty; this.length = length; this.add = add; this.addRange = addRange; this.finish = finish; } public override object Read(object value, ProtoReader source) { object obj = builderFactory.Invoke(null, null); int field = source.FieldNumber; object[] array = new object[1]; if (base.AppendToCollection && value != null && (((object)isEmpty != null) ? (!(bool)isEmpty.GetValue(value, null)) : ((byte)(int)length.GetValue(value, null) != 0))) { if ((object)addRange != null) { array[0] = value; addRange.Invoke(obj, array); } else { foreach (object item in (ICollection)value) { array[0] = item; add.Invoke(obj, array); } } } if (packedWireType != WireType.None && source.WireType == WireType.String) { SubItemToken token = ProtoReader.StartSubItem(source); while (ProtoReader.HasSubValue(packedWireType, source)) { array[0] = Tail.Read(null, source); add.Invoke(obj, array); } ProtoReader.EndSubItem(token, source); } else { do { array[0] = Tail.Read(null, source); add.Invoke(obj, array); } while (source.TryReadFieldHeader(field)); } return finish.Invoke(obj, null); } protected override void EmitRead(CompilerContext ctx, Local valueFrom) { using Local local = (base.AppendToCollection ? ctx.GetLocalWithValue(ExpectedType, valueFrom) : null); using Local local2 = new Local(ctx, builderFactory.ReturnType); ctx.EmitCall(builderFactory); ctx.StoreValue(local2); if (base.AppendToCollection) { CodeLabel label = ctx.DefineLabel(); if (!Helpers.IsValueType(ExpectedType)) { ctx.LoadValue(local); ctx.BranchIfFalse(label, @short: false); } ctx.LoadAddress(local, local.Type); if ((object)isEmpty != null) { ctx.EmitCall(Helpers.GetGetMethod(isEmpty, nonPublic: false, allowInternal: false)); ctx.BranchIfTrue(label, @short: false); } else { ctx.EmitCall(Helpers.GetGetMethod(length, nonPublic: false, allowInternal: false)); ctx.BranchIfFalse(label, @short: false); } Type type = ctx.MapType(typeof(void)); if ((object)addRange != null) { ctx.LoadValue(local2); ctx.LoadValue(local); ctx.EmitCall(addRange); if ((object)addRange.ReturnType != null && (object)add.ReturnType != type) { ctx.DiscardValue(); } } else { MethodInfo moveNext; MethodInfo current; MethodInfo enumeratorInfo = GetEnumeratorInfo(ctx.Model, out moveNext, out current); Type returnType = enumeratorInfo.ReturnType; using Local local3 = new Local(ctx, returnType); ctx.LoadAddress(local, ExpectedType); ctx.EmitCall(enumeratorInfo); ctx.StoreValue(local3); using (ctx.Using(local3)) { CodeLabel label2 = ctx.DefineLabel(); CodeLabel label3 = ctx.DefineLabel(); ctx.Branch(label3, @short: false); ctx.MarkLabel(label2); ctx.LoadAddress(local2, local2.Type); ctx.LoadAddress(local3, returnType); ctx.EmitCall(current); ctx.EmitCall(add); if ((object)add.ReturnType != null && (object)add.ReturnType != type) { ctx.DiscardValue(); } ctx.MarkLabel(label3); ctx.LoadAddress(local3, returnType); ctx.EmitCall(moveNext); ctx.BranchIfTrue(label2, @short: false); } } ctx.MarkLabel(label); } ListDecorator.EmitReadList(ctx, local2, Tail, add, packedWireType, castListForAdd: false); ctx.LoadAddress(local2, local2.Type); ctx.EmitCall(finish); if ((object)ExpectedType != finish.ReturnType) { ctx.Cast(ExpectedType); } } }