Exemple #1
0
        /// <summary>
        /// Enumerate members which will be deserialized for the given type.
        ///
        /// Note that these members are generated ahead of time with a source gneerator,
        ///   and cannot be changed at runtime.
        /// </summary>
        public IEnumerable <DeserializableMember> EnumerateMembersToDeserialize(TypeInfo forType)
        {
            var paired = GetPairedType(forType, DESERIALIZER_KIND);

            if (paired == null)
            {
                return(Enumerable.Empty <DeserializableMember>());
            }

            var colNamesProp = paired.GetPropertyNonNull("__ColumnNames", PublicStatic);
            var colNames     = (ImmutableArray <string>)colNamesProp.GetValueNonNull(null);

            var ret = ImmutableArray.CreateBuilder <DeserializableMember>(colNames.Length);

            ParameterInfo[]? consPs = null;

            for (var i = 0; i < colNames.Length; i++)
            {
                var name = colNames[i];

                var colReaderName = $"__Column_{i}";
                var colReaderMtd  = paired.GetMethodNonNull(colReaderName, PublicInstance);

#pragma warning disable CS0618 // These are obsolete to prevent clients from using them, but they are fine for us.
                var isRequired = colReaderMtd.GetCustomAttribute <IsRequiredAttribute>() != null;
                var setterBackedByParameter = colReaderMtd.GetCustomAttribute <SetterBackedByConstructorParameterAttribute>();
                var setterIsInitOnly        = colReaderMtd.GetCustomAttribute <SetterBackedByInitOnlyPropertyAttribute>();
#pragma warning restore CS0618

                Setter setter;
                if (setterBackedByParameter == null && setterIsInitOnly == null)
                {
                    // directly a method
                    var setterName = $"__Column_{i}_Setter";
                    var setterMtd  = paired.GetMethodNonNull(setterName, PublicStatic);
                    setter = Setter.ForMethod(setterMtd);
                }
                else if (setterBackedByParameter != null)
                {
                    // parameter to constructor
                    if (consPs == null)
                    {
                        var ip = GetInstanceProvider(forType);
                        ip = Utils.NonNull(ip);

                        var cons = ip.Constructor.Value;
                        consPs = cons.GetParameters();
                    }

                    var consParameterIndex = setterBackedByParameter.Index;
                    if (consParameterIndex < 0 || consParameterIndex >= consPs.Length)
                    {
                        Throw.ImpossibleException($"Setter for column {i} claims to be backed by constructor parameter, but its position is out of bounds (index={consParameterIndex})");
                    }

                    var p = consPs[setterBackedByParameter.Index];
                    setter = Setter.ForConstructorParameter(p);
                }
                else
                {
                    // init only property
                    var initOnly = Utils.NonNull(setterIsInitOnly);

                    var prop = forType.GetProperty(initOnly.PropertyName, initOnly.BindingFlags);
                    if (prop == null)
                    {
                        Throw.ImpossibleException($"Setter for column {i} claims to be backed by init-only property {initOnly.PropertyName} with bindings ({initOnly.BindingFlags}), but it could not be found");
                    }

                    setter = Setter.ForProperty(prop);
                }

                var resetMethodName = $"__Column_{i}_Reset";
                var resetMtd        = paired.GetMethod(resetMethodName, PublicStatic);
                var reset           = (Reset?)resetMtd;

                var    parserMethodName = $"__Column_{i}_Parser";
                var    parserMtd        = paired.GetMethod(parserMethodName, PublicStatic);
                Parser?parser;
                if (parserMtd != null)
                {
                    parser = Parser.ForMethod(parserMtd);
                }
                else
                {
                    parser = Utils.NonNull(Parser.GetDefault(setter.Takes));
                }

                ret.Add(DeserializableMember.CreateInner(forType, name, setter, parser, isRequired ? MemberRequired.Yes : MemberRequired.No, reset, paired));
            }

            return(ret.ToImmutable());
        }
        private static SerializableMember Create(TypeInfo beingSerializedType, string name, MethodInfo getter, FieldInfo field, MethodInfo formatter, MethodInfo shouldSerialize, bool emitDefaultValue)
        {
            if (beingSerializedType == null)
            {
                Throw.ArgumentNullException(nameof(beingSerializedType));
            }

            if (name == null)
            {
                Throw.ArgumentNullException(nameof(name));
            }

            if (field == null && getter == null)
            {
                Throw.InvalidOperationException($"At least one of {nameof(field)} and {nameof(getter)} must be non-null");
            }

            if (field != null && getter != null)
            {
                Throw.InvalidOperationException($"Only one of {nameof(field)} and {nameof(getter)} can be non-null");
            }

            if (formatter == null)
            {
                Throw.ArgumentNullException(nameof(formatter));
            }

            TypeInfo toSerializeType;

            // getter can be an instance method or a static method
            //   if it's a static method, it can take 0 or 1 parameters
            //      the 1 parameter must be the type to be serialized, or something it is assignable to
            //   if it's an instance method, it can only take 0 parameters
            if (getter != null)
            {
                if (getter.ReturnType == Types.VoidType)
                {
                    Throw.ArgumentException($"{nameof(getter)} must return a non-void value", nameof(getter));
                }

                var getterParams = getter.GetParameters();

                if (getter.IsStatic)
                {
                    if (getterParams.Length == 0)
                    {
                        /* that's fine */
                    }
                    else if (getterParams.Length == 1)
                    {
                        var takenParam = getterParams[0].ParameterType.GetTypeInfo();
                        if (!takenParam.IsAssignableFrom(beingSerializedType))
                        {
                            Throw.ArgumentException($"{getter}'s single parameter must be assignable from {beingSerializedType}", nameof(getter));
                        }
                    }
                    else
                    {
                        Throw.ArgumentException($"Since {getter} is a static method, it cannot take more than 1 parameter", nameof(getter));
                    }
                }
                else
                {
                    if (getterParams.Length > 0)
                    {
                        Throw.ArgumentException($"Since {getter} is an instance method, it cannot take any parameters", nameof(getter));
                    }
                }

                toSerializeType = getter.ReturnType.GetTypeInfo();
            }
            else
            {
                toSerializeType = field.FieldType.GetTypeInfo();
            }

            // formatter needs to take the toSerializeType (or a type it's assignable to)
            //   and an IBufferWriter<char>
            //   and a WriteContext
            //   and return bool (false indicates insufficient space was available)
            {
                if (!formatter.IsStatic)
                {
                    Throw.ArgumentException($"{nameof(formatter)} must be a static method", nameof(formatter));
                }

                var formatterRetType = formatter.ReturnType.GetTypeInfo();
                if (formatterRetType != Types.BoolType)
                {
                    Throw.ArgumentException($"{nameof(formatter)} must return bool", nameof(formatter));
                }

                var args = formatter.GetParameters();
                if (args.Length != 3)
                {
                    Throw.ArgumentException($"{nameof(formatter)} must take 3 parameters", nameof(formatter));
                }

                if (!args[0].ParameterType.IsAssignableFrom(toSerializeType))
                {
                    Throw.ArgumentException($"The first paramater to {nameof(formatter)} must be accept a {toSerializeType}", nameof(formatter));
                }

                var p2 = args[1].ParameterType.GetTypeInfo();
                if (!p2.IsByRef)
                {
                    Throw.ArgumentException($"The second paramater to {nameof(formatter)} must be an in {nameof(WriteContext)}, was not by ref", nameof(formatter));
                }

                if (p2.GetElementType() != Types.WriteContext)
                {
                    Throw.ArgumentException($"The second paramater to {nameof(formatter)} must be an in {nameof(WriteContext)}", nameof(formatter));
                }

                if (args[2].ParameterType.GetTypeInfo() != Types.IBufferWriterOfCharType)
                {
                    Throw.ArgumentException($"The third paramater to {nameof(formatter)} must be a {nameof(IBufferWriter<char>)}", nameof(formatter));
                }
            }

            var shouldSerializeOnType = (getter?.DeclaringType ?? field?.DeclaringType).GetTypeInfo();

            CheckShouldSerializeMethod(shouldSerialize, shouldSerializeOnType);

            return(new SerializableMember(name, getter, field, formatter, shouldSerialize, emitDefaultValue));
        }