// create a delegate that will format the given value (pulled from a getter or a field) into // a buffer, subject to shouldSerialize being null or returning true // and return true if it was able to do so internal static ColumnWriterDelegate Create(TypeInfo type, Options options, Formatter formatter, NonNull<ShouldSerialize> shouldSerialize, Getter getter, bool emitDefaultValue) { var p1 = Expressions.Parameter_Object; var p2 = Expressions.Parameter_WriteContext_ByRef; var p3 = Expressions.Parameter_IBufferWriterOfChar; var statements = new List<Expression>(); var p1AsType = Expression.Convert(p1, type); var l1 = Expression.Variable(type, "l1"); var assignToL1 = Expression.Assign(l1, p1AsType); statements.Add(assignToL1); var end = Expression.Label(Types.Bool, "end"); var returnTrue = Expression.Label("return-true"); if (shouldSerialize.HasValue) { var ss = shouldSerialize.Value; if (ss.TakesNullability == NullHandling.ForbidNull && ss.Takes.Value.AllowsNullLikeValue()) { var takes = ss.Takes.Value; var checkExp = Utils.MakeNullHandlingCheckExpression(takes, l1, $"{ss} does not accept null rows, but was given one at runtime"); statements.Add(checkExp); } var callShouldSerialize = ss.MakeExpression(l1, p2); var shouldntSerialize = Expression.Not(callShouldSerialize); var jumpToEnd = Expression.Goto(returnTrue); var jumpToEndIfShouldntSerialize = Expression.IfThen(shouldntSerialize, jumpToEnd); statements.Add(jumpToEndIfShouldntSerialize); } var columnType = getter.Returns; var l2 = Expression.Variable(columnType, "l2"); var getExp = getter.MakeExpression(l1, p2); var assignToL2 = Expression.Assign(l2, getExp); statements.Add(assignToL2); if (getter.ReturnsNullability == NullHandling.ForbidNull && columnType.AllowsNullLikeValue()) { // do we need to check that a null didn't happen at runtime? var checkExp = Utils.MakeNullHandlingCheckExpression(getter.Returns, l2, $"{getter} was forbidden from return null values, but did return one at runtime"); statements.Add(checkExp); } if (!emitDefaultValue) { ConstantExpression defValue; if (columnType.IsValueType) { defValue = Expression.Constant(Activator.CreateInstance(columnType)); } else { defValue = Expression.Constant(null, columnType); } Expression isDefault; // intentionally letting GetMethod return null here if (!columnType.IsValueType || columnType.IsEnum || columnType.IsPrimitive || columnType.GetMethod("op_Equality") != null) { isDefault = Expression.Equal(l2, defValue); } else { var equatableI = Types.IEquatable.MakeGenericType(columnType).GetTypeInfo(); if (columnType.GetInterfaces().Any(i => i == equatableI)) { var equals = equatableI.GetMethodNonNull(nameof(IEquatable<object>.Equals)); var map = columnType.GetInterfaceMap(equatableI); MethodInfo? equalsTyped = null; for (var j = 0; j < map.InterfaceMethods.Length; j++) { if (map.InterfaceMethods[j] == equals) { equalsTyped = map.TargetMethods[j]; isDefault = Expression.Call(l2, equalsTyped, defValue); goto done; } } Throw.ImpossibleException($"Could not find typed {nameof(IEquatable<object>.Equals)} method, which shouldn't be possible", options); return default; } else { var eqsUntyped = columnType.GetMethodNonNull(nameof(object.Equals)); var defAsObject = Expression.Convert(defValue, Types.Object); isDefault = Expression.Call(l2, eqsUntyped, defAsObject); } } done: var ifIsDefaultReturnTrue = Expression.IfThen(isDefault, Expression.Goto(returnTrue)); statements.Add(ifIsDefaultReturnTrue); } if (formatter.TakesNullability == NullHandling.ForbidNull && formatter.Takes.AllowsNullLikeValue()) { // make sure formatter invariant hasn't been violated var checkExp = Utils.MakeNullHandlingCheckExpression(formatter.Takes, l2, $"{formatter} does not take null values, but received one at runtime"); statements.Add(checkExp); } var callFormatter = formatter.MakeExpression(l2, p2, p3); statements.Add(Expression.Goto(end, callFormatter)); statements.Add(Expression.Label(returnTrue)); statements.Add(Expression.Goto(end, Expressions.Constant_True)); statements.Add(Expression.Label(end, Expressions.Constant_False)); var block = Expression.Block(new[] { l1, l2 }, statements); var del = Expression.Lambda<ColumnWriterDelegate>(block, p1, p2, p3); var compiled = del.Compile(); return compiled; }
private static void CheckShouldSerializeMethod(ShouldSerialize?shouldSerialize, NonNull <TypeInfo> onTypeNull) { if (shouldSerialize == null) { return; } if (shouldSerialize.Takes.HasValue) { var shouldSerializeInstType = shouldSerialize.Takes.Value; var onType = onTypeNull.Value; var isInstOrSubclass = onType.IsAssignableFrom(shouldSerializeInstType); if (!isInstOrSubclass) { Throw.ArgumentException($"{nameof(shouldSerialize)} must be either static method taking no parameters, a static method taking the type being serialized, an instance method on the type being serialized, or a delegate taking the type being serialized", nameof(shouldSerialize)); } } }