private static DeserializableMember Map(TypeInfo ontoType, DeserializableMember member)
        {
            MethodInfo?resetOnType = null;

            if (member.Reset.HasValue)
            {
                var surrogateResetWrapper = member.Reset.Value;
                if (surrogateResetWrapper.Mode != BackingMode.Method)
                {
                    Throw.InvalidOperationException($"Cannot map reset {surrogateResetWrapper} onto {ontoType}, reset isn't backed by a method");
                }

                var surrogateReset = surrogateResetWrapper.Method.Value;

                var surrogateResetBinding = GetEquivalentFlagsFor(surrogateReset.IsPublic, surrogateReset.IsStatic);

                Type[] resetTakesTypes;
                if (surrogateResetWrapper.TakesContext)
                {
                    resetTakesTypes = new[] { Types.ReadContext.MakeByRefType() };
                }
                else
                {
                    resetTakesTypes = Type.EmptyTypes;
                }

                // intentionally letting this be null
                resetOnType = ontoType.GetMethod(surrogateReset.Name, surrogateResetBinding, null, resetTakesTypes, null);
                if (resetOnType == null)
                {
                    Throw.InvalidOperationException($"No equivalent to {resetOnType} found on {ontoType}");
                }
            }

            var surrogateSetterWrapper = member.Setter;

            switch (surrogateSetterWrapper.Mode)
            {
            case BackingMode.Field:
            {
                var surrogateField        = surrogateSetterWrapper.Field.Value;
                var surrogateFieldBinding = GetEquivalentFlagsFor(surrogateField.IsPublic, surrogateField.IsStatic);

                // intentionally allowing null here
                var fieldOnType = ontoType.GetField(surrogateField.Name, surrogateFieldBinding);
                if (fieldOnType == null)
                {
                    Throw.InvalidOperationException($"No equivalent to {surrogateField} found on {ontoType}");
                }

                if (fieldOnType.FieldType != surrogateField.FieldType)
                {
                    Throw.InvalidOperationException($"Field {fieldOnType} type ({fieldOnType.FieldType}) does not match surrogate field {surrogateField} type ({surrogateField.FieldType})");
                }

                var required = GetEquivalentRequiredFor(member.IsRequired);

                return(DeserializableMember.CreateInner(ontoType, member.Name, (Setter?)fieldOnType, member.Parser, required, (Reset?)resetOnType, null));
            }

            case BackingMode.Method:
            {
                var surrogateSetter = surrogateSetterWrapper.Method.Value;

                var surrogateSetterBinding = GetEquivalentFlagsFor(surrogateSetter.IsPublic, surrogateSetter.IsStatic);

                // intentionally letting this be null
                var setterOnType = ontoType.GetMethod(surrogateSetter.Name, surrogateSetterBinding);
                if (setterOnType == null)
                {
                    Throw.InvalidOperationException($"No equivalent to {surrogateSetter} found on {ontoType}");
                }

                var paramsOnType      = setterOnType.GetParameters();
                var paramsOnSurrogate = surrogateSetter.GetParameters();

                if (paramsOnType.Length != paramsOnSurrogate.Length)
                {
                    Throw.InvalidOperationException($"Parameters for {setterOnType} do not match parameters for {surrogateSetter}");
                }

                for (var i = 0; i < paramsOnType.Length; i++)
                {
                    var pOnType      = paramsOnType[i];
                    var pOnSurrogate = paramsOnSurrogate[i];

                    if (pOnType.ParameterType != pOnSurrogate.ParameterType)
                    {
                        Throw.InvalidOperationException($"Parameter #{(i + 1)} on {setterOnType} does not match same parameter on {surrogateSetter}");
                    }
                }

                var required = GetEquivalentRequiredFor(member.IsRequired);

                return(DeserializableMember.CreateInner(ontoType, member.Name, (Setter?)setterOnType, member.Parser, required, (Reset?)resetOnType, null));
            }

            case BackingMode.Delegate:
                Throw.InvalidOperationException($"Cannot map setter {surrogateSetterWrapper} onto {ontoType}, setter is backed by a delegate");
                return(default);

            default:
                Throw.InvalidOperationException($"Unexpected {nameof(BackingMode)}: {surrogateSetterWrapper.Mode}");
                return(default);
            }
        }
Example #2
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());
        }