Skip to content

Commit 087f42f

Browse files
committed
Fix bug in ToExpressionString with lifted binary expressions
also slightly improved the constant's ToCode: * there was a duplicated check if it's possible to write the constant, so I moved it to one place. This fixes a problem that it could not print `Nullable<int>` which was quite annoying. * typeof(Nullable<int>) is now printed as typeof(int?) * `(int)1` is unnecessary, so now it will print just `1` * basic support for DateTime, TimeSpan, Guid constants * `new LabelTarget[0]` and similar are not part of the output unless it's required. it just makes it less intimidating to me 😅
1 parent 797c2a9 commit 087f42f

File tree

4 files changed

+136
-19
lines changed

4 files changed

+136
-19
lines changed

src/FastExpressionCompiler/FastExpressionCompiler.cs

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5603,9 +5603,12 @@ public static string ToExpressionString(this Expression expr,
56035603
lts = new List<LabelTarget>();
56045604
sb = expr.CreateExpressionString(sb, paramsExprs, uniqueExprs, lts, 2, stripNamespace, printType, identSpaces, tryPrintConstant).Append(';');
56055605

5606-
sb.Insert(0, $"var l = new LabelTarget[{lts.Count}]; // the labels {NewLine}");
5607-
sb.Insert(0, $"var e = new Expression[{uniqueExprs.Count}]; // the unique expressions {NewLine}");
5608-
sb.Insert(0, $"var p = new ParameterExpression[{paramsExprs.Count}]; // the parameter expressions {NewLine}");
5606+
if (lts.Count > 0)
5607+
sb.Insert(0, $"var l = new LabelTarget[{lts.Count}]; // the labels {NewLine}");
5608+
if (uniqueExprs.Count > 0)
5609+
sb.Insert(0, $"var e = new Expression[{uniqueExprs.Count}]; // the unique expressions {NewLine}");
5610+
if (paramsExprs.Count > 0)
5611+
sb.Insert(0, $"var p = new ParameterExpression[{paramsExprs.Count}]; // the parameter expressions {NewLine}");
56095612

56105613
return sb.ToString();
56115614
}
@@ -5795,21 +5798,9 @@ internal static StringBuilder CreateExpressionString(this Expression e, StringBu
57955798
sb.AppendTypeof(t, stripNamespace, printType);
57965799
else
57975800
{
5798-
// For the closure bound constant let's output `null` or default value with the comment for user to provide the actual value
5799-
if (ExpressionCompiler.IsClosureBoundConstant(x.Value, x.Type))
5800-
{
5801-
if (x.Type.IsValueType)
5802-
sb.Append("default(").Append(x.Type.ToCode(stripNamespace, printType)).Append(')');
5803-
else // specifying the type for the Constant, otherwise we will lost it with the `Constant(default(MyClass))` which is equivalent to `Constant(null)`
5804-
sb.Append("null, ").AppendTypeof(x.Type, stripNamespace, printType);
5805-
sb.NewLineIdent(lineIdent).Append("// !!! Please provide the non-default value").NewLineIdent(lineIdent);
5806-
}
5807-
else
5808-
{
5809-
sb.Append(x.Value.ToCode(CodePrinter.DefaultConstantValueToCode, stripNamespace, printType));
5810-
if (x.Value.GetType() != x.Type)
5811-
sb.Append(", ").AppendTypeof(x.Type, stripNamespace, printType);
5812-
}
5801+
sb.Append(x.Value.ToCode(CodePrinter.DefaultConstantValueToCode, stripNamespace, printType));
5802+
if (x.Value.GetType() != x.Type)
5803+
sb.Append(", ").AppendTypeof(x.Type, stripNamespace, printType);
58135804
}
58145805
return sb.Append(')');
58155806
}
@@ -6098,6 +6089,15 @@ internal static StringBuilder CreateExpressionString(this Expression e, StringBu
60986089
sb.Append("MakeBinary(").Append(typeof(ExpressionType).Name).Append('.').Append(name).Append(',');
60996090
sb.NewLineIdentExpr(b.Left, paramsExprs, uniqueExprs, lts, lineIdent, stripNamespace, printType, identSpaces, tryPrintConstant).Append(',');
61006091
sb.NewLineIdentExpr(b.Right, paramsExprs, uniqueExprs, lts, lineIdent, stripNamespace, printType, identSpaces, tryPrintConstant);
6092+
if (b.IsLiftedToNull || b.Method != null || b.Conversion != null)
6093+
{
6094+
sb.Append(',').NewLineIdent(lineIdent).Append("liftToNull: ").Append(b.IsLiftedToNull.ToCode()).Append(',');
6095+
sb.NewLineIdent(lineIdent).AppendMethod(b.Method, stripNamespace, printType);
6096+
}
6097+
if (b.Conversion != null)
6098+
{
6099+
sb.Append(',').NewLineIdentExpr(b.Conversion, paramsExprs, uniqueExprs, lts, lineIdent, stripNamespace, printType, identSpaces, tryPrintConstant);
6100+
}
61016101
}
61026102
return sb.Append(')');
61036103
}
@@ -7052,6 +7052,12 @@ public static string ToCode(this Type type,
70527052
return !printGenericTypeArgs ? string.Empty
70537053
: (printType?.Invoke(type, type.Name) ?? type.Name);
70547054

7055+
if (Nullable.GetUnderlyingType(type) is Type nullableElementType && !type.IsGenericTypeDefinition)
7056+
{
7057+
var result = nullableElementType.ToCode(stripNamespace, printType, printGenericTypeArgs) + "?";
7058+
return printType?.Invoke(type, result) ?? result;
7059+
}
7060+
70557061
Type arrayType = null;
70567062
if (type.IsArray)
70577063
{
@@ -7224,7 +7230,7 @@ public interface IObjectToCode
72247230
private class ConstantValueToCode : CodePrinter.IObjectToCode
72257231
{
72267232
public string ToCode(object x, bool stripNamespace = false, Func<Type, string, string> printType = null) =>
7227-
"default(" + x.GetType().ToCode(stripNamespace, printType) + ")";
7233+
"default(" + x.GetType().ToCode(stripNamespace, printType) + ") /* " + x.ToString() + " !!! Please provide the non-default value */ ";
72287234
}
72297235

72307236

@@ -7267,6 +7273,12 @@ public static string ToCode(this object x, IObjectToCode notRecognizedToCode,
72677273
if (x is bool b)
72687274
return b.ToCode();
72697275

7276+
if (x is int i)
7277+
return i.ToString();
7278+
7279+
if (x is double d)
7280+
return d.ToString();
7281+
72707282
if (x is string s)
72717283
return s.ToCode();
72727284

@@ -7276,6 +7288,15 @@ public static string ToCode(this object x, IObjectToCode notRecognizedToCode,
72767288
if (x is Type t)
72777289
return t.ToCode(stripNamespace, printType);
72787290

7291+
if (x is Guid guid)
7292+
return "Guid.Parse(" + guid.ToString().ToCode() + ")";
7293+
7294+
if (x is DateTime date)
7295+
return "DateTime.Parse(" + date.ToString().ToCode() + ")";
7296+
7297+
if (x is TimeSpan time)
7298+
return "TimeSpan.Parse(" + time.ToString().ToCode() + ")";
7299+
72797300
var xType = x.GetType();
72807301
var xTypeInfo = xType.GetTypeInfo();
72817302

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using NUnit.Framework;
2+
using System;
3+
using System.Linq.Expressions;
4+
#if LIGHT_EXPRESSION
5+
using static FastExpressionCompiler.LightExpression.Expression;
6+
namespace FastExpressionCompiler.LightExpression.IssueTests
7+
#else
8+
using static System.Linq.Expressions.Expression;
9+
namespace FastExpressionCompiler.IssueTests
10+
#endif
11+
{
12+
[TestFixture]
13+
public class Issue314_LiftToNull_ToExpressionString
14+
{
15+
public int Run()
16+
{
17+
LiftToNull();
18+
CustomMethod();
19+
DateTimeConstant();
20+
NullableDateTimeConstant();
21+
return 4;
22+
}
23+
24+
[Test]
25+
public void LiftToNull()
26+
{
27+
var p = Parameter(typeof(int), "tmp0");
28+
var expr = Expression.MakeBinary(
29+
ExpressionType.Add,
30+
Expression.Constant(1, typeof(int?)),
31+
Expression.Constant(null, typeof(int?)),
32+
liftToNull: true,
33+
null
34+
);
35+
36+
var str = expr.ToExpressionString(out _, out _, out _, true);
37+
Console.WriteLine(str);
38+
Assert.AreEqual(@"
39+
var e = new Expression[2]; // the unique expressions
40+
var expr = MakeBinary(ExpressionType.Add,
41+
e[0]=Constant(1, typeof(int?)),
42+
e[1]=Constant(null, typeof(int?)),
43+
liftToNull: true,
44+
null);
45+
".Trim(), str);
46+
}
47+
48+
[Test]
49+
public void CustomMethod()
50+
{
51+
var x = Parameter(typeof(A), "x");
52+
var expr = Expression.Add(x, x);
53+
var str = expr.ToExpressionString(out _, out _, out _, true);
54+
Console.WriteLine(str);
55+
Assert.AreEqual(@"
56+
var p = new ParameterExpression[1]; // the parameter expressions
57+
var expr = MakeBinary(ExpressionType.Add,
58+
p[0]=Parameter(typeof(Issue314_LiftToNull_ToExpressionString.A), ""x""),
59+
p[0 // (Issue314_LiftToNull_ToExpressionString.A x)
60+
],
61+
liftToNull: false,
62+
typeof(Issue314_LiftToNull_ToExpressionString.A).GetMethods().Single(x => !x.IsGenericMethod && x.Name == ""op_Addition"" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(Issue314_LiftToNull_ToExpressionString.A), typeof(Issue314_LiftToNull_ToExpressionString.A) })));
63+
".Trim(), str);
64+
}
65+
[Test]
66+
public void DateTimeConstant()
67+
{
68+
var expr = Expression.Constant(new DateTime(2020, 3, 13));
69+
var str = expr.ToExpressionString();
70+
Console.WriteLine(str);
71+
Assert.AreEqual(@"
72+
var expr = Constant(DateTime.Parse(""3/13/2020 12:00:00 AM""));
73+
".Trim(), str);
74+
}
75+
[Test]
76+
public void NullableDateTimeConstant()
77+
{
78+
var expr = Expression.Constant(new DateTime(2020, 3, 13), typeof(DateTime?));
79+
var str = expr.ToExpressionString();
80+
Console.WriteLine(str);
81+
Assert.AreEqual(@"
82+
var expr = Constant(DateTime.Parse(""3/13/2020 12:00:00 AM""), typeof(System.DateTime?));
83+
".Trim(), str);
84+
}
85+
86+
class A {
87+
public static A operator +(A x, A y) { return new A(); }
88+
}
89+
}
90+
}

test/FastExpressionCompiler.TestsRunner.Net472/Program.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ void Run(Func<int> run, string name = null)
204204
Run(new Issue310_InvalidProgramException_ignored_nullable().Run);
205205
Run(new FastExpressionCompiler.LightExpression.IssueTests.Issue310_InvalidProgramException_ignored_nullable().Run);
206206

207+
Run(new Issue314_LiftToNull_ToExpressionString().Run);
208+
Run(new FastExpressionCompiler.LightExpression.IssueTests.Issue314_LiftToNull_ToExpressionString().Run);
209+
207210
Console.WriteLine($"============={Environment.NewLine}IssueTests are passing in {sw.ElapsedMilliseconds} ms.");
208211
});
209212

test/FastExpressionCompiler.TestsRunner/Program.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ void Run(Func<int> run, string name = null)
229229
Run(new Issue310_InvalidProgramException_ignored_nullable().Run);
230230
Run(new FastExpressionCompiler.LightExpression.IssueTests.Issue310_InvalidProgramException_ignored_nullable().Run);
231231

232+
Run(new Issue314_LiftToNull_ToExpressionString().Run);
233+
Run(new FastExpressionCompiler.LightExpression.IssueTests.Issue314_LiftToNull_ToExpressionString().Run);
234+
232235
Console.WriteLine($"============={Environment.NewLine}IssueTests are passing in {sw.ElapsedMilliseconds} ms.");
233236
});
234237

0 commit comments

Comments
 (0)