Skip to content

Commit 64141ec

Browse files
author
Maksim Volkau
committed
2 parents 0f7bb37 + 9091afd commit 64141ec

9 files changed

Lines changed: 910 additions & 7 deletions

File tree

src/FastExpressionCompiler/FastExpressionCompiler.cs

Lines changed: 626 additions & 0 deletions
Large diffs are not rendered by default.

test/FastExpressionCompiler.IssueTests/Issue261_Loop_wih_conditions_fails.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ public void Serialize_the_nullable_decimal_array()
277277
var sysExpr = expr.ToLambdaExpression();
278278
var restoredExpr = sysExpr.ToLightExpression();
279279
restoredExpr.PrintCSharp();
280-
Asserts.AreEqual(expr.ToCSharpString(), restoredExpr.ToCSharpString());
280+
Asserts.IsTrue(expr.EqualsTo(restoredExpr));
281281
#endif
282282

283283
var fs = expr.CompileSys();

test/FastExpressionCompiler.IssueTests/Issue274_Failing_Expressions_in_Linq2DB.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,7 @@ public void Test_case_2_Full_ExecutionEngineException()
271271
#if LIGHT_EXPRESSION
272272
var sysExpr = expr.ToLambdaExpression();
273273
var restoredExpr = sysExpr.ToLightExpression();
274-
// todo: @feature #431 compare the restored target and source expressions directly instead of strings
275-
Asserts.AreEqual(expr.ToCSharpString(), restoredExpr.ToCSharpString());
274+
Asserts.IsTrue(expr.EqualsTo(restoredExpr));
276275
#endif
277276

278277
var fs = expr.CompileSys();

test/FastExpressionCompiler.IssueTests/Issue430_TryCatch_Bad_label_content_in_ILGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public void Original_case()
4242
var sysExpr = expr.ToLambdaExpression();
4343
var restoredExpr = sysExpr.ToLightExpression();
4444
restoredExpr.PrintCSharp();
45-
Asserts.AreEqual(expr.ToCSharpString(), restoredExpr.ToCSharpString());
45+
Asserts.IsTrue(expr.EqualsTo(restoredExpr));
4646
#endif
4747

4848
var fs = expr.CompileSys();
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
6+
#if LIGHT_EXPRESSION
7+
using static FastExpressionCompiler.LightExpression.Expression;
8+
using FastExpressionCompiler.LightExpression;
9+
using FastExpressionCompiler.LightExpression.ImTools;
10+
namespace FastExpressionCompiler.LightExpression.IssueTests;
11+
#else
12+
using static System.Linq.Expressions.Expression;
13+
using FastExpressionCompiler.ImTools;
14+
namespace FastExpressionCompiler.IssueTests;
15+
#endif
16+
17+
public struct Issue431_Add_structural_equality_comparison_to_LightExpression : ITestX
18+
{
19+
public static readonly ConstructorInfo CtorOfA = typeof(A).GetTypeInfo().DeclaredConstructors.First();
20+
public static readonly ConstructorInfo CtorOfB = typeof(B).GetTypeInfo().DeclaredConstructors.First();
21+
public static readonly PropertyInfo PropAProp = typeof(A).GetTypeInfo().DeclaredProperties.First(p => p.Name == "Prop");
22+
23+
public void Run(TestRun t)
24+
{
25+
Eq_simple_lambda(t);
26+
Eq_lambda_with_parameters(t);
27+
Eq_constants(t);
28+
Eq_member_access(t);
29+
Eq_method_call(t);
30+
Eq_new_expression(t);
31+
Eq_member_init(t);
32+
Eq_new_array(t);
33+
Eq_conditional(t);
34+
Eq_block_with_variables(t);
35+
Eq_try_catch(t);
36+
Eq_loop_with_labels(t);
37+
Eq_switch(t);
38+
#if LIGHT_EXPRESSION
39+
Eq_complex_lambda_round_trip(t);
40+
#endif
41+
Hash_equal_expressions_have_equal_hashes(t);
42+
Hash_used_as_dictionary_key(t);
43+
Hash_used_as_smallmap_key(t);
44+
NotEq_different_constants(t);
45+
NotEq_different_types(t);
46+
NotEq_different_parameters(t);
47+
}
48+
49+
public void Eq_simple_lambda(TestContext t)
50+
{
51+
var e1 = Lambda<Func<int>>(Constant(42));
52+
var e2 = Lambda<Func<int>>(Constant(42));
53+
t.IsTrue(e1.EqualsTo(e2));
54+
}
55+
56+
public void Eq_lambda_with_parameters(TestContext t)
57+
{
58+
var p1a = Parameter(typeof(int), "x");
59+
var p1b = Parameter(typeof(int), "y");
60+
var e1 = Lambda<Func<int, int, int>>(Add(p1a, p1b), p1a, p1b);
61+
62+
var p2a = Parameter(typeof(int), "x");
63+
var p2b = Parameter(typeof(int), "y");
64+
var e2 = Lambda<Func<int, int, int>>(Add(p2a, p2b), p2a, p2b);
65+
66+
t.IsTrue(e1.EqualsTo(e2));
67+
}
68+
69+
public void Eq_constants(TestContext t)
70+
{
71+
t.IsTrue(Constant(42).EqualsTo(Constant(42)));
72+
t.IsTrue(Constant("hello").EqualsTo(Constant("hello")));
73+
t.IsTrue(Constant(null, typeof(string)).EqualsTo(Constant(null, typeof(string))));
74+
}
75+
76+
public void Eq_member_access(TestContext t)
77+
{
78+
var prop = typeof(string).GetProperty(nameof(string.Length));
79+
var p1 = Parameter(typeof(string), "s");
80+
var p2 = Parameter(typeof(string), "s");
81+
var e1 = Lambda<Func<string, int>>(Property(p1, prop), p1);
82+
var e2 = Lambda<Func<string, int>>(Property(p2, prop), p2);
83+
t.IsTrue(e1.EqualsTo(e2));
84+
}
85+
86+
public void Eq_method_call(TestContext t)
87+
{
88+
var method = typeof(string).GetMethod(nameof(string.Concat), new[] { typeof(string), typeof(string) });
89+
var p1 = Parameter(typeof(string), "a");
90+
var p2 = Parameter(typeof(string), "b");
91+
var pa = Parameter(typeof(string), "a");
92+
var pb = Parameter(typeof(string), "b");
93+
var e1 = Lambda<Func<string, string, string>>(Call(method, p1, p2), p1, p2);
94+
var e2 = Lambda<Func<string, string, string>>(Call(method, pa, pb), pa, pb);
95+
t.IsTrue(e1.EqualsTo(e2));
96+
}
97+
98+
public void Eq_new_expression(TestContext t)
99+
{
100+
var ctor = typeof(B).GetConstructor(Type.EmptyTypes);
101+
var e1 = New(ctor);
102+
var e2 = New(ctor);
103+
t.IsTrue(e1.EqualsTo(e2));
104+
}
105+
106+
public void Eq_member_init(TestContext t)
107+
{
108+
var e1 = MemberInit(New(CtorOfA, New(CtorOfB)), Bind(PropAProp, New(CtorOfB)));
109+
var e2 = MemberInit(New(CtorOfA, New(CtorOfB)), Bind(PropAProp, New(CtorOfB)));
110+
t.IsTrue(e1.EqualsTo(e2));
111+
}
112+
113+
public void Eq_new_array(TestContext t)
114+
{
115+
var e1 = NewArrayInit(typeof(int), Constant(1), Constant(2), Constant(3));
116+
var e2 = NewArrayInit(typeof(int), Constant(1), Constant(2), Constant(3));
117+
t.IsTrue(e1.EqualsTo(e2));
118+
}
119+
120+
public void Eq_conditional(TestContext t)
121+
{
122+
var p1 = Parameter(typeof(int), "x");
123+
var p2 = Parameter(typeof(int), "x");
124+
var e1 = Lambda<Func<int, int>>(Condition(Equal(p1, Constant(0)), Constant(1), p1), p1);
125+
var e2 = Lambda<Func<int, int>>(Condition(Equal(p2, Constant(0)), Constant(1), p2), p2);
126+
t.IsTrue(e1.EqualsTo(e2));
127+
}
128+
129+
public void Eq_block_with_variables(TestContext t)
130+
{
131+
var v1 = Variable(typeof(int), "i");
132+
var v2 = Variable(typeof(int), "i");
133+
var e1 = Block(new[] { v1 }, Assign(v1, Constant(5)), v1);
134+
var e2 = Block(new[] { v2 }, Assign(v2, Constant(5)), v2);
135+
t.IsTrue(e1.EqualsTo(e2));
136+
}
137+
138+
public void Eq_try_catch(TestContext t)
139+
{
140+
var ex1 = Parameter(typeof(Exception), "ex");
141+
var ex2 = Parameter(typeof(Exception), "ex");
142+
var e1 = TryCatch(Constant(1), Catch(ex1, Constant(2)));
143+
var e2 = TryCatch(Constant(1), Catch(ex2, Constant(2)));
144+
t.IsTrue(e1.EqualsTo(e2));
145+
}
146+
147+
public void Eq_loop_with_labels(TestContext t)
148+
{
149+
var brk1 = Label(typeof(void), "break");
150+
var cnt1 = Label(typeof(void), "continue");
151+
var brk2 = Label(typeof(void), "break");
152+
var cnt2 = Label(typeof(void), "continue");
153+
var e1 = Loop(Block(Break(brk1), Continue(cnt1)), brk1, cnt1);
154+
var e2 = Loop(Block(Break(brk2), Continue(cnt2)), brk2, cnt2);
155+
t.IsTrue(e1.EqualsTo(e2));
156+
}
157+
158+
public void Eq_switch(TestContext t)
159+
{
160+
var p1 = Parameter(typeof(int), "x");
161+
var p2 = Parameter(typeof(int), "x");
162+
var e1 = Lambda<Func<int, int>>(
163+
Switch(p1, Constant(-1), SwitchCase(Constant(10), Constant(1)), SwitchCase(Constant(20), Constant(2))),
164+
p1);
165+
var e2 = Lambda<Func<int, int>>(
166+
Switch(p2, Constant(-1), SwitchCase(Constant(10), Constant(1)), SwitchCase(Constant(20), Constant(2))),
167+
p2);
168+
t.IsTrue(e1.EqualsTo(e2));
169+
}
170+
171+
#if LIGHT_EXPRESSION
172+
public void Eq_complex_lambda_round_trip(TestContext t)
173+
{
174+
var expr = Lambda<Func<object[], object>>(
175+
MemberInit(
176+
New(CtorOfA, New(CtorOfB)),
177+
Bind(PropAProp, New(CtorOfB))),
178+
Parameter(typeof(object[]), "p"));
179+
180+
var sysExpr = expr.ToLambdaExpression();
181+
var restoredExpr = sysExpr.ToLightExpression<Func<object[], object>>();
182+
183+
t.IsTrue(expr.EqualsTo(restoredExpr));
184+
}
185+
#endif
186+
187+
public void Hash_equal_expressions_have_equal_hashes(TestContext t)
188+
{
189+
// Structural equality implies equal hashes (mandatory contract for use as dictionary key).
190+
var p1 = Parameter(typeof(int), "x");
191+
var p2 = Parameter(typeof(int), "y"); // different name — structurally same
192+
var e1 = Lambda<Func<int, int>>(Add(p1, Constant(1)), p1);
193+
var e2 = Lambda<Func<int, int>>(Add(p2, Constant(1)), p2);
194+
t.IsTrue(e1.EqualsTo(e2));
195+
t.AreEqual(ExpressionEqualityComparer.GetHashCode(e1), ExpressionEqualityComparer.GetHashCode(e2));
196+
197+
// Constants
198+
t.AreEqual(ExpressionEqualityComparer.GetHashCode(Constant(42)), ExpressionEqualityComparer.GetHashCode(Constant(42)));
199+
200+
// Different constants must have different hashes (not guaranteed in general, but these are obviously distinct)
201+
t.AreNotEqual(ExpressionEqualityComparer.GetHashCode(Constant(1)), ExpressionEqualityComparer.GetHashCode(Constant(2)));
202+
}
203+
204+
public void Hash_used_as_dictionary_key(TestContext t)
205+
{
206+
// Verify that structurally-equal expressions resolve to the same Dictionary bucket.
207+
var cmp = default(ExpressionEqualityComparer);
208+
var dict = new Dictionary<
209+
#if LIGHT_EXPRESSION
210+
FastExpressionCompiler.LightExpression.Expression,
211+
#else
212+
System.Linq.Expressions.Expression,
213+
#endif
214+
string>(cmp);
215+
216+
var p1 = Parameter(typeof(int), "x");
217+
var e1 = Lambda<Func<int, int>>(Add(p1, Constant(1)), p1);
218+
dict[e1] = "found";
219+
220+
var p2 = Parameter(typeof(int), "y"); // different identity/name
221+
var e2 = Lambda<Func<int, int>>(Add(p2, Constant(1)), p2);
222+
t.IsTrue(dict.TryGetValue(e2, out var v));
223+
t.AreEqual("found", v);
224+
}
225+
226+
public void Hash_used_as_smallmap_key(TestContext t)
227+
{
228+
// Verify lookup via SmallMap8 which uses GetHashCode + Equals internally.
229+
var p1 = Parameter(typeof(int), "x");
230+
var e1 = Lambda<Func<int, int>>(Add(p1, Constant(1)), p1);
231+
var h1 = ExpressionEqualityComparer.GetHashCode(e1);
232+
233+
var p2 = Parameter(typeof(int), "y");
234+
var e2 = Lambda<Func<int, int>>(Add(p2, Constant(1)), p2);
235+
var h2 = ExpressionEqualityComparer.GetHashCode(e2);
236+
237+
// Structurally equal ⟹ same hash
238+
t.AreEqual(h1, h2);
239+
240+
// Structurally different ⟹ different hash (for obviously distinct constants)
241+
var e3 = Lambda<Func<int, int>>(Add(p1, Constant(99)), p1);
242+
t.AreNotEqual(h1, ExpressionEqualityComparer.GetHashCode(e3));
243+
}
244+
245+
public void NotEq_different_constants(TestContext t)
246+
{
247+
t.IsFalse(Constant(42).EqualsTo(Constant(43)));
248+
t.IsFalse(Constant("a").EqualsTo(Constant("b")));
249+
}
250+
251+
public void NotEq_different_types(TestContext t)
252+
{
253+
t.IsFalse(Constant(42).EqualsTo(Constant(42L)));
254+
t.IsFalse(Default(typeof(int)).EqualsTo(Default(typeof(long))));
255+
}
256+
257+
public void NotEq_different_parameters(TestContext t)
258+
{
259+
var p1 = Parameter(typeof(int), "x");
260+
var p2 = Parameter(typeof(int), "y");
261+
var e1 = Lambda<Func<int, int>>(p1, p1);
262+
var e2 = Lambda<Func<int, int>>(p2, p2);
263+
// When mapped by position in a lambda, different-named params ARE equal structurally (same position)
264+
t.IsTrue(e1.EqualsTo(e2));
265+
// But accessing a param outside its lambda context uses name comparison
266+
t.IsFalse(p1.EqualsTo(p2));
267+
}
268+
269+
public class A
270+
{
271+
public B Prop { get; set; }
272+
public A(B b) { Prop = b; }
273+
}
274+
275+
public class B { }
276+
}

test/FastExpressionCompiler.LightExpression.IssueTests/Issue363_ActionFunc16Generics.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void Can_Create_Func(int paramCount)
7878
var sysExpr = expr.ToLambdaExpression();
7979
var restoredExpr = sysExpr.ToLightExpression();
8080
restoredExpr.PrintCSharp();
81-
Asserts.AreEqual(expr.ToCSharpString(), restoredExpr.ToCSharpString());
81+
Asserts.IsTrue(expr.EqualsTo(restoredExpr));
8282
#endif
8383

8484
// (a, b, c, ...) => new[] { a, b, c, ... }

test/FastExpressionCompiler.TestsRunner.Net472/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ static void RunLightExpressionTests(object state)
3636
var t = (LightExpression.TestRun)state;
3737
t.Run(new LightExpression.IssueTests.Issue183_NullableDecimal());
3838
t.Run(new LightExpression.IssueTests.Issue398_Optimize_Switch_with_OpCodes_Switch());
39+
t.Run(new LightExpression.IssueTests.Issue431_Add_structural_equality_comparison_to_LightExpression());
3940
t.Run(new LightExpression.IssueTests.Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET());
4041
t.Run(new LightExpression.IssueTests.Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_expressions_during_the_compilation());
4142
t.Run(new LightExpression.IssueTests.Issue473_InvalidProgramException_when_using_Expression_Condition_with_converted_decimal_expression());

test/FastExpressionCompiler.TestsRunner/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public static void Main()
5555
st.Run(new Issue498_InvalidProgramException_when_using_loop());
5656
st.Run(new Issue495_Incomplete_pattern_detection_for_NotSupported_1007_Return_goto_from_TryCatch_with_Assign_generates_invalid_IL());
5757
st.Run(new Issue480_CLR_detected_an_invalid_program_exception());
58+
st.Run(new Issue431_Add_structural_equality_comparison_to_LightExpression());
5859
#if NET8_0_OR_GREATER
5960
st.Run(new Issue487_Fix_ToCSharpString_output_for_boolean_equality_expressions());
6061
st.Run(new Issue475_Reuse_DynamicMethod_if_possible());
@@ -67,6 +68,7 @@ public static void Main()
6768
#endif
6869

6970
lt.Run(new LightExpression.IssueTests.Issue398_Optimize_Switch_with_OpCodes_Switch());
71+
lt.Run(new LightExpression.IssueTests.Issue431_Add_structural_equality_comparison_to_LightExpression());
7072
lt.Run(new LightExpression.IssueTests.Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET());
7173
lt.Run(new LightExpression.IssueTests.Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_expressions_during_the_compilation());
7274
lt.Run(new LightExpression.IssueTests.Issue473_InvalidProgramException_when_using_Expression_Condition_with_converted_decimal_expression());

test/FastExpressionCompiler.UnitTests/AssignTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,7 @@ public void Array_multi_dimensional_index_assign_value_type_block()
386386
var sysExpr = expr.ToLambdaExpression();
387387
var restoredExpr = sysExpr.ToLightExpression();
388388
restoredExpr.PrintCSharp();
389-
// todo: @wip #431 generates different names for the unnamed variables which is not comparable
390-
Asserts.AreEqual(expr.ToCSharpString(), restoredExpr.ToCSharpString());
389+
Asserts.IsTrue(expr.EqualsTo(restoredExpr));
391390
#endif
392391
Asserts.IsNotNull(fs);
393392
Asserts.AreEqual(5, fs());

0 commit comments

Comments
 (0)