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