/// <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)); }