From dfe78dbf07bc5f54839ef6505a10084fcbc32ac3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 12 Apr 2026 07:42:24 +0000
Subject: [PATCH 1/7] Initial plan
From a3b49354aae70b3d1db26054920fc4dd6a6f5749 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 12 Apr 2026 07:55:40 +0000
Subject: [PATCH 2/7] Add ExpressionEqualityComparer with EqualsTo extension
method for LightExpression
Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/961c247d-26e0-4577-9e1c-7d227466ebaa
Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
---
.../FastExpressionCompiler.cs | 402 ++++++++++++++++++
.../Issue261_Loop_wih_conditions_fails.cs | 2 +-
...Issue274_Failing_Expressions_in_Linq2DB.cs | 3 +-
...yCatch_Bad_label_content_in_ILGenerator.cs | 2 +-
..._equality_comparison_to_LightExpression.cs | 217 ++++++++++
.../Issue363_ActionFunc16Generics.cs | 2 +-
.../Program.cs | 2 +
.../Program.cs | 2 +
.../AssignTests.cs | 3 +-
9 files changed, 628 insertions(+), 7 deletions(-)
create mode 100644 test/FastExpressionCompiler.IssueTests/Issue431_Add_structural_equality_comparison_to_LightExpression.cs
diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs
index 24caf5dd..3f930b7a 100644
--- a/src/FastExpressionCompiler/FastExpressionCompiler.cs
+++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs
@@ -10000,6 +10000,408 @@ internal static StringBuilder CreateExpressionString(this Expression e, StringBu
}
}
+#if LIGHT_EXPRESSION
+ /// Provides structural equality comparison for the LightExpression.
+ public static class ExpressionEqualityComparer
+ {
+ /// Structurally compares two expressions.
+ /// Parameters are matched by their position within their enclosing lambda, and label targets by identity pairing.
+ /// No heap allocations for expressions with up to 4 lambda parameters or label targets.
+ public static bool EqualsTo(this Expression x, Expression y)
+ {
+ SmallList xps = default, yps = default;
+ SmallList xls = default, yls = default;
+ return Eq(x, y, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ private static bool Eq(Expression x, Expression y,
+ ref SmallList xps, ref SmallList yps,
+ ref SmallList xls, ref SmallList yls)
+ {
+ if (ReferenceEquals(x, y)) return true;
+ if (x == null | y == null) return false;
+ if (x.NodeType != y.NodeType | x.Type != y.Type) return false;
+ switch (x.NodeType)
+ {
+ case ExpressionType.Parameter:
+ {
+ var px = (ParameterExpression)x;
+ var py = (ParameterExpression)y;
+ for (var i = 0; i < xps.Count; i++)
+ if (ReferenceEquals(xps.Items[i], px))
+ return ReferenceEquals(yps.Items[i], py);
+ // unmapped — compare structurally (Type already checked)
+ return px.IsByRef == py.IsByRef && px.Name == py.Name;
+ }
+
+ case ExpressionType.Constant:
+ {
+ var cx = (ConstantExpression)x;
+ var cy = (ConstantExpression)y;
+ return Equals(cx.Value, cy.Value);
+ }
+
+ case ExpressionType.Lambda:
+ {
+ var lx = (LambdaExpression)x;
+ var ly = (LambdaExpression)y;
+ var pc = lx.ParameterCount;
+ if (pc != ly.ParameterCount) return false;
+ var sc = xps.Count;
+ for (var i = 0; i < pc; i++)
+ {
+ xps.AddDefaultAndGetRef() = lx.GetParameter(i);
+ yps.AddDefaultAndGetRef() = ly.GetParameter(i);
+ }
+ var eq = Eq(lx.Body, ly.Body, ref xps, ref yps, ref xls, ref yls);
+ xps.Count = sc;
+ yps.Count = sc;
+ return eq;
+ }
+
+ case ExpressionType.Negate: case ExpressionType.NegateChecked:
+ case ExpressionType.UnaryPlus: case ExpressionType.Not:
+ case ExpressionType.ArrayLength: case ExpressionType.TypeAs:
+ case ExpressionType.Convert: case ExpressionType.ConvertChecked:
+ case ExpressionType.Quote: case ExpressionType.Throw:
+ case ExpressionType.OnesComplement: case ExpressionType.IsTrue: case ExpressionType.IsFalse:
+ case ExpressionType.Increment: case ExpressionType.Decrement:
+ case ExpressionType.PreIncrementAssign: case ExpressionType.PostIncrementAssign:
+ case ExpressionType.PreDecrementAssign: case ExpressionType.PostDecrementAssign:
+ case ExpressionType.Unbox:
+ {
+ var ux = (UnaryExpression)x;
+ var uy = (UnaryExpression)y;
+ return ux.Method == uy.Method &&
+ Eq(ux.Operand, uy.Operand, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ case ExpressionType.Add: case ExpressionType.AddChecked:
+ case ExpressionType.Subtract: case ExpressionType.SubtractChecked:
+ case ExpressionType.Multiply: case ExpressionType.MultiplyChecked:
+ case ExpressionType.Divide: case ExpressionType.Modulo:
+ case ExpressionType.Power: case ExpressionType.And:
+ case ExpressionType.Or: case ExpressionType.ExclusiveOr:
+ case ExpressionType.LeftShift: case ExpressionType.RightShift:
+ case ExpressionType.AndAlso: case ExpressionType.OrElse:
+ case ExpressionType.Equal: case ExpressionType.NotEqual:
+ case ExpressionType.LessThan: case ExpressionType.LessThanOrEqual:
+ case ExpressionType.GreaterThan: case ExpressionType.GreaterThanOrEqual:
+ case ExpressionType.Coalesce: case ExpressionType.ArrayIndex:
+ case ExpressionType.Assign:
+ case ExpressionType.AddAssign: case ExpressionType.AddAssignChecked:
+ case ExpressionType.SubtractAssign: case ExpressionType.SubtractAssignChecked:
+ case ExpressionType.MultiplyAssign: case ExpressionType.MultiplyAssignChecked:
+ case ExpressionType.DivideAssign: case ExpressionType.ModuloAssign:
+ case ExpressionType.PowerAssign: case ExpressionType.AndAssign:
+ case ExpressionType.OrAssign: case ExpressionType.ExclusiveOrAssign:
+ case ExpressionType.LeftShiftAssign: case ExpressionType.RightShiftAssign:
+ {
+ var bx = (BinaryExpression)x;
+ var by = (BinaryExpression)y;
+ return bx.Method == by.Method &&
+ Eq(bx.Conversion, by.Conversion, ref xps, ref yps, ref xls, ref yls) &&
+ Eq(bx.Left, by.Left, ref xps, ref yps, ref xls, ref yls) &&
+ Eq(bx.Right, by.Right, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ case ExpressionType.Call:
+ {
+ var mx = (MethodCallExpression)x;
+ var my = (MethodCallExpression)y;
+ return mx.Method == my.Method &&
+ Eq(mx.Object, my.Object, ref xps, ref yps, ref xls, ref yls) &&
+ EqArgs(mx, my, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ case ExpressionType.MemberAccess:
+ {
+ var fx = (MemberExpression)x;
+ var fy = (MemberExpression)y;
+ return fx.Member == fy.Member &&
+ Eq(fx.Expression, fy.Expression, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ case ExpressionType.New:
+ {
+ var nx = (NewExpression)x;
+ var ny = (NewExpression)y;
+ return nx.Constructor == ny.Constructor &&
+ EqArgs(nx, ny, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ case ExpressionType.NewArrayInit:
+ case ExpressionType.NewArrayBounds:
+ {
+ var nx = (NewArrayExpression)x;
+ var ny = (NewArrayExpression)y;
+ return EqArgs(nx, ny, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ case ExpressionType.Conditional:
+ {
+ var cx = (ConditionalExpression)x;
+ var cy = (ConditionalExpression)y;
+ return Eq(cx.Test, cy.Test, ref xps, ref yps, ref xls, ref yls) &&
+ Eq(cx.IfTrue, cy.IfTrue, ref xps, ref yps, ref xls, ref yls) &&
+ Eq(cx.IfFalse, cy.IfFalse, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ case ExpressionType.Block:
+ {
+ var bx = (BlockExpression)x;
+ var by = (BlockExpression)y;
+ var vc = bx.Variables.Count;
+ if (vc != by.Variables.Count) return false;
+ var ec = bx.Expressions.Count;
+ if (ec != by.Expressions.Count) return false;
+ var sc = xps.Count;
+ for (var i = 0; i < vc; i++)
+ {
+ xps.AddDefaultAndGetRef() = bx.Variables[i];
+ yps.AddDefaultAndGetRef() = by.Variables[i];
+ }
+ var eq = true;
+ for (var i = 0; i < ec && eq; i++)
+ eq = Eq(bx.Expressions.GetSurePresentRef(i), by.Expressions.GetSurePresentRef(i),
+ ref xps, ref yps, ref xls, ref yls);
+ xps.Count = sc;
+ yps.Count = sc;
+ return eq;
+ }
+
+ case ExpressionType.MemberInit:
+ {
+ var mx = (MemberInitExpression)x;
+ var my = (MemberInitExpression)y;
+ var bc = mx.Bindings.Count;
+ if (bc != my.Bindings.Count) return false;
+ if (!Eq(mx.Expression, my.Expression, ref xps, ref yps, ref xls, ref yls)) return false;
+ for (var i = 0; i < bc; i++)
+ if (!EqBinding(mx.Bindings[i], my.Bindings[i], ref xps, ref yps, ref xls, ref yls)) return false;
+ return true;
+ }
+
+ case ExpressionType.ListInit:
+ {
+ var lx = (ListInitExpression)x;
+ var ly = (ListInitExpression)y;
+ var ic = lx.Initializers.Count;
+ if (ic != ly.Initializers.Count) return false;
+ if (!Eq(lx.NewExpression, ly.NewExpression, ref xps, ref yps, ref xls, ref yls)) return false;
+ for (var i = 0; i < ic; i++)
+ if (!EqElementInit(lx.Initializers[i], ly.Initializers[i], ref xps, ref yps, ref xls, ref yls)) return false;
+ return true;
+ }
+
+ case ExpressionType.TypeIs:
+ case ExpressionType.TypeEqual:
+ {
+ var tx = (TypeBinaryExpression)x;
+ var ty = (TypeBinaryExpression)y;
+ return tx.TypeOperand == ty.TypeOperand &&
+ Eq(tx.Expression, ty.Expression, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ case ExpressionType.Invoke:
+ {
+ var ix = (InvocationExpression)x;
+ var iy = (InvocationExpression)y;
+ return Eq(ix.Expression, iy.Expression, ref xps, ref yps, ref xls, ref yls) &&
+ EqArgs(ix, iy, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ case ExpressionType.Index:
+ {
+ var ix = (IndexExpression)x;
+ var iy = (IndexExpression)y;
+ return ix.Indexer == iy.Indexer &&
+ Eq(ix.Object, iy.Object, ref xps, ref yps, ref xls, ref yls) &&
+ EqArgs(ix, iy, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ case ExpressionType.Default:
+ return true; // Type already matched above
+
+ case ExpressionType.Label:
+ {
+ var lx = (LabelExpression)x;
+ var ly = (LabelExpression)y;
+ return EqLabel(lx.Target, ly.Target, ref xls, ref yls) &&
+ Eq(lx.DefaultValue, ly.DefaultValue, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ case ExpressionType.Goto:
+ {
+ var gx = (GotoExpression)x;
+ var gy = (GotoExpression)y;
+ return gx.Kind == gy.Kind &&
+ EqLabel(gx.Target, gy.Target, ref xls, ref yls) &&
+ Eq(gx.Value, gy.Value, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ case ExpressionType.Loop:
+ {
+ var lx = (LoopExpression)x;
+ var ly = (LoopExpression)y;
+ return EqLabel(lx.BreakLabel, ly.BreakLabel, ref xls, ref yls) &&
+ EqLabel(lx.ContinueLabel, ly.ContinueLabel, ref xls, ref yls) &&
+ Eq(lx.Body, ly.Body, ref xps, ref yps, ref xls, ref yls);
+ }
+
+ case ExpressionType.Try:
+ {
+ var tx = (TryExpression)x;
+ var ty = (TryExpression)y;
+ if (!Eq(tx.Body, ty.Body, ref xps, ref yps, ref xls, ref yls)) return false;
+ if (!Eq(tx.Finally, ty.Finally, ref xps, ref yps, ref xls, ref yls)) return false;
+ if (!Eq(tx.Fault, ty.Fault, ref xps, ref yps, ref xls, ref yls)) return false;
+ var hc = tx.Handlers.Count;
+ if (hc != ty.Handlers.Count) return false;
+ for (var i = 0; i < hc; i++)
+ {
+ var hx = tx.Handlers[i];
+ var hy = ty.Handlers[i];
+ if (hx.Test != hy.Test) return false;
+ var sc = xps.Count;
+ if (hx.Variable != null | hy.Variable != null)
+ {
+ if (hx.Variable == null | hy.Variable == null) return false;
+ if (hx.Variable.Type != hy.Variable.Type) return false;
+ xps.AddDefaultAndGetRef() = hx.Variable;
+ yps.AddDefaultAndGetRef() = hy.Variable;
+ }
+ var ceq = Eq(hx.Body, hy.Body, ref xps, ref yps, ref xls, ref yls) &&
+ Eq(hx.Filter, hy.Filter, ref xps, ref yps, ref xls, ref yls);
+ xps.Count = sc;
+ yps.Count = sc;
+ if (!ceq) return false;
+ }
+ return true;
+ }
+
+ case ExpressionType.Switch:
+ {
+ var sx = (SwitchExpression)x;
+ var sy = (SwitchExpression)y;
+ if (sx.Comparison != sy.Comparison) return false;
+ if (!Eq(sx.SwitchValue, sy.SwitchValue, ref xps, ref yps, ref xls, ref yls)) return false;
+ if (!Eq(sx.DefaultBody, sy.DefaultBody, ref xps, ref yps, ref xls, ref yls)) return false;
+ var cc = sx.Cases.Count;
+ if (cc != sy.Cases.Count) return false;
+ for (var i = 0; i < cc; i++)
+ {
+ var cx = sx.Cases[i];
+ var cy = sy.Cases[i];
+ if (!Eq(cx.Body, cy.Body, ref xps, ref yps, ref xls, ref yls)) return false;
+ var tc = cx.TestValues.Count;
+ if (tc != cy.TestValues.Count) return false;
+ for (var j = 0; j < tc; j++)
+ if (!Eq(cx.TestValues[j], cy.TestValues[j], ref xps, ref yps, ref xls, ref yls)) return false;
+ }
+ return true;
+ }
+
+ case ExpressionType.RuntimeVariables:
+ {
+ var rx = (RuntimeVariablesExpression)x;
+ var ry = (RuntimeVariablesExpression)y;
+ var vc = rx.Variables.Count;
+ if (vc != ry.Variables.Count) return false;
+ for (var i = 0; i < vc; i++)
+ if (!Eq(rx.Variables[i], ry.Variables[i], ref xps, ref yps, ref xls, ref yls)) return false;
+ return true;
+ }
+
+ case ExpressionType.DebugInfo:
+ {
+ var dx = (DebugInfoExpression)x;
+ var dy = (DebugInfoExpression)y;
+ return dx.IsClear == dy.IsClear &&
+ dx.StartLine == dy.StartLine && dx.StartColumn == dy.StartColumn &&
+ dx.EndLine == dy.EndLine && dx.EndColumn == dy.EndColumn &&
+ dx.Document?.FileName == dy.Document?.FileName;
+ }
+
+ default:
+ return false;
+ }
+ }
+
+ private static bool EqLabel(LabelTarget x, LabelTarget y,
+ ref SmallList xls, ref SmallList yls)
+ {
+ if (ReferenceEquals(x, y)) return true;
+ if (x == null | y == null) return false;
+ if (x.Type != y.Type) return false;
+ for (var i = 0; i < xls.Count; i++)
+ if (ReferenceEquals(xls.Items[i], x))
+ return ReferenceEquals(yls.Items[i], y);
+ // Register the pair and compare by name
+ xls.AddDefaultAndGetRef() = x;
+ yls.AddDefaultAndGetRef() = y;
+ return x.Name == y.Name;
+ }
+
+ private static bool EqArgs(IArgumentProvider x, IArgumentProvider y,
+ ref SmallList xps, ref SmallList yps,
+ ref SmallList xls, ref SmallList yls)
+ {
+ var c = x.ArgumentCount;
+ if (c != y.ArgumentCount) return false;
+ for (var i = 0; i < c; i++)
+ if (!Eq(x.GetArgument(i), y.GetArgument(i), ref xps, ref yps, ref xls, ref yls)) return false;
+ return true;
+ }
+
+ private static bool EqElementInit(ElementInit x, ElementInit y,
+ ref SmallList xps, ref SmallList yps,
+ ref SmallList xls, ref SmallList yls)
+ {
+ if (x.AddMethod != y.AddMethod) return false;
+ var ac = x.ArgumentCount;
+ if (ac != y.ArgumentCount) return false;
+ for (var i = 0; i < ac; i++)
+ if (!Eq(x.GetArgument(i), y.GetArgument(i), ref xps, ref yps, ref xls, ref yls)) return false;
+ return true;
+ }
+
+ private static bool EqBinding(MemberBinding x, MemberBinding y,
+ ref SmallList xps, ref SmallList yps,
+ ref SmallList xls, ref SmallList yls)
+ {
+ if (x.BindingType != y.BindingType | x.Member != y.Member) return false;
+ switch (x.BindingType)
+ {
+ case MemberBindingType.Assignment:
+ return Eq(((MemberAssignment)x).Expression, ((MemberAssignment)y).Expression,
+ ref xps, ref yps, ref xls, ref yls);
+ case MemberBindingType.MemberBinding:
+ {
+ var mb = (MemberMemberBinding)x;
+ var mbOther = (MemberMemberBinding)y;
+ var bc = mb.Bindings.Count;
+ if (bc != mbOther.Bindings.Count) return false;
+ for (var i = 0; i < bc; i++)
+ if (!EqBinding(mb.Bindings[i], mbOther.Bindings[i], ref xps, ref yps, ref xls, ref yls)) return false;
+ return true;
+ }
+ case MemberBindingType.ListBinding:
+ {
+ var lb = (MemberListBinding)x;
+ var lbOther = (MemberListBinding)y;
+ var ic = lb.Initializers.Count;
+ if (ic != lbOther.Initializers.Count) return false;
+ for (var i = 0; i < ic; i++)
+ if (!EqElementInit(lb.Initializers[i], lbOther.Initializers[i], ref xps, ref yps, ref xls, ref yls)) return false;
+ return true;
+ }
+ default: return false;
+ }
+ }
+ }
+#endif
+
/// Converts the expression into the valid C# code representation
[RequiresUnreferencedCode(Trimming.Message)]
public static class ToCSharpPrinter
diff --git a/test/FastExpressionCompiler.IssueTests/Issue261_Loop_wih_conditions_fails.cs b/test/FastExpressionCompiler.IssueTests/Issue261_Loop_wih_conditions_fails.cs
index c6c9cb59..b47df6ef 100644
--- a/test/FastExpressionCompiler.IssueTests/Issue261_Loop_wih_conditions_fails.cs
+++ b/test/FastExpressionCompiler.IssueTests/Issue261_Loop_wih_conditions_fails.cs
@@ -277,7 +277,7 @@ public void Serialize_the_nullable_decimal_array()
var sysExpr = expr.ToLambdaExpression();
var restoredExpr = sysExpr.ToLightExpression();
restoredExpr.PrintCSharp();
- Asserts.AreEqual(expr.ToCSharpString(), restoredExpr.ToCSharpString());
+ Asserts.IsTrue(expr.EqualsTo(restoredExpr));
#endif
var fs = expr.CompileSys();
diff --git a/test/FastExpressionCompiler.IssueTests/Issue274_Failing_Expressions_in_Linq2DB.cs b/test/FastExpressionCompiler.IssueTests/Issue274_Failing_Expressions_in_Linq2DB.cs
index bfc126b0..73be1e77 100644
--- a/test/FastExpressionCompiler.IssueTests/Issue274_Failing_Expressions_in_Linq2DB.cs
+++ b/test/FastExpressionCompiler.IssueTests/Issue274_Failing_Expressions_in_Linq2DB.cs
@@ -271,8 +271,7 @@ public void Test_case_2_Full_ExecutionEngineException()
#if LIGHT_EXPRESSION
var sysExpr = expr.ToLambdaExpression();
var restoredExpr = sysExpr.ToLightExpression();
- // todo: @feature #431 compare the restored target and source expressions directly instead of strings
- Asserts.AreEqual(expr.ToCSharpString(), restoredExpr.ToCSharpString());
+ Asserts.IsTrue(expr.EqualsTo(restoredExpr));
#endif
var fs = expr.CompileSys();
diff --git a/test/FastExpressionCompiler.IssueTests/Issue430_TryCatch_Bad_label_content_in_ILGenerator.cs b/test/FastExpressionCompiler.IssueTests/Issue430_TryCatch_Bad_label_content_in_ILGenerator.cs
index 34620737..78a92bb5 100644
--- a/test/FastExpressionCompiler.IssueTests/Issue430_TryCatch_Bad_label_content_in_ILGenerator.cs
+++ b/test/FastExpressionCompiler.IssueTests/Issue430_TryCatch_Bad_label_content_in_ILGenerator.cs
@@ -42,7 +42,7 @@ public void Original_case()
var sysExpr = expr.ToLambdaExpression();
var restoredExpr = sysExpr.ToLightExpression();
restoredExpr.PrintCSharp();
- Asserts.AreEqual(expr.ToCSharpString(), restoredExpr.ToCSharpString());
+ Asserts.IsTrue(expr.EqualsTo(restoredExpr));
#endif
var fs = expr.CompileSys();
diff --git a/test/FastExpressionCompiler.IssueTests/Issue431_Add_structural_equality_comparison_to_LightExpression.cs b/test/FastExpressionCompiler.IssueTests/Issue431_Add_structural_equality_comparison_to_LightExpression.cs
new file mode 100644
index 00000000..e7d1c30d
--- /dev/null
+++ b/test/FastExpressionCompiler.IssueTests/Issue431_Add_structural_equality_comparison_to_LightExpression.cs
@@ -0,0 +1,217 @@
+using System;
+using System.Linq;
+using System.Reflection;
+
+
+#if LIGHT_EXPRESSION
+using static FastExpressionCompiler.LightExpression.Expression;
+using FastExpressionCompiler.LightExpression;
+namespace FastExpressionCompiler.LightExpression.IssueTests;
+#else
+using static System.Linq.Expressions.Expression;
+namespace FastExpressionCompiler.IssueTests;
+#endif
+
+
+#if LIGHT_EXPRESSION
+public class Issue431_Add_structural_equality_comparison_to_LightExpression : ITest
+{
+ public int Run()
+ {
+ Eq_simple_lambda();
+ Eq_lambda_with_parameters();
+ Eq_constants();
+ Eq_member_access();
+ Eq_method_call();
+ Eq_new_expression();
+ Eq_member_init();
+ Eq_new_array();
+ Eq_conditional();
+ Eq_block_with_variables();
+ Eq_try_catch();
+ Eq_loop_with_labels();
+ Eq_switch();
+ Eq_complex_lambda_round_trip();
+ NotEq_different_constants();
+ NotEq_different_types();
+ NotEq_different_parameters();
+ return 17;
+ }
+
+ public void Eq_simple_lambda()
+ {
+ var e1 = Lambda>(Constant(42));
+ var e2 = Lambda>(Constant(42));
+ Asserts.IsTrue(e1.EqualsTo(e2));
+ }
+
+ public void Eq_lambda_with_parameters()
+ {
+ var p1a = Parameter(typeof(int), "x");
+ var p1b = Parameter(typeof(int), "y");
+ var e1 = Lambda>(Add(p1a, p1b), p1a, p1b);
+
+ var p2a = Parameter(typeof(int), "x");
+ var p2b = Parameter(typeof(int), "y");
+ var e2 = Lambda>(Add(p2a, p2b), p2a, p2b);
+
+ Asserts.IsTrue(e1.EqualsTo(e2));
+ }
+
+ public void Eq_constants()
+ {
+ Asserts.IsTrue(Constant(42).EqualsTo(Constant(42)));
+ Asserts.IsTrue(Constant("hello").EqualsTo(Constant("hello")));
+ Asserts.IsTrue(Constant(null, typeof(string)).EqualsTo(Constant(null, typeof(string))));
+ }
+
+ public void Eq_member_access()
+ {
+ var prop = typeof(string).GetProperty(nameof(string.Length));
+ var p1 = Parameter(typeof(string), "s");
+ var p2 = Parameter(typeof(string), "s");
+ var e1 = Lambda>(Property(p1, prop), p1);
+ var e2 = Lambda>(Property(p2, prop), p2);
+ Asserts.IsTrue(e1.EqualsTo(e2));
+ }
+
+ public void Eq_method_call()
+ {
+ 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>(Call(method, p1, p2), p1, p2);
+ var e2 = Lambda>(Call(method, pa, pb), pa, pb);
+ Asserts.IsTrue(e1.EqualsTo(e2));
+ }
+
+ public void Eq_new_expression()
+ {
+ var ctor = typeof(B).GetConstructor(Type.EmptyTypes);
+ var e1 = New(ctor);
+ var e2 = New(ctor);
+ Asserts.IsTrue(e1.EqualsTo(e2));
+ }
+
+ public static ConstructorInfo CtorOfA = typeof(A).GetTypeInfo().DeclaredConstructors.First();
+ public static ConstructorInfo CtorOfB = typeof(B).GetTypeInfo().DeclaredConstructors.First();
+ public static PropertyInfo PropAProp = typeof(A).GetTypeInfo().DeclaredProperties.First(p => p.Name == "Prop");
+
+ public void Eq_member_init()
+ {
+ var e1 = MemberInit(New(CtorOfA, New(CtorOfB)), Bind(PropAProp, New(CtorOfB)));
+ var e2 = MemberInit(New(CtorOfA, New(CtorOfB)), Bind(PropAProp, New(CtorOfB)));
+ Asserts.IsTrue(e1.EqualsTo(e2));
+ }
+
+ public void Eq_new_array()
+ {
+ var e1 = NewArrayInit(typeof(int), Constant(1), Constant(2), Constant(3));
+ var e2 = NewArrayInit(typeof(int), Constant(1), Constant(2), Constant(3));
+ Asserts.IsTrue(e1.EqualsTo(e2));
+ }
+
+ public void Eq_conditional()
+ {
+ var p1 = Parameter(typeof(int), "x");
+ var p2 = Parameter(typeof(int), "x");
+ var e1 = Lambda>(Condition(Equal(p1, Constant(0)), Constant(1), p1), p1);
+ var e2 = Lambda>(Condition(Equal(p2, Constant(0)), Constant(1), p2), p2);
+ Asserts.IsTrue(e1.EqualsTo(e2));
+ }
+
+ public void Eq_block_with_variables()
+ {
+ 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);
+ Asserts.IsTrue(e1.EqualsTo(e2));
+ }
+
+ public void Eq_try_catch()
+ {
+ 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)));
+ Asserts.IsTrue(e1.EqualsTo(e2));
+ }
+
+ public void Eq_loop_with_labels()
+ {
+ 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);
+ Asserts.IsTrue(e1.EqualsTo(e2));
+ }
+
+ public void Eq_switch()
+ {
+ var p1 = Parameter(typeof(int), "x");
+ var p2 = Parameter(typeof(int), "x");
+ var e1 = Lambda>(
+ Switch(p1, Constant(-1), SwitchCase(Constant(10), Constant(1)), SwitchCase(Constant(20), Constant(2))),
+ p1);
+ var e2 = Lambda>(
+ Switch(p2, Constant(-1), SwitchCase(Constant(10), Constant(1)), SwitchCase(Constant(20), Constant(2))),
+ p2);
+ Asserts.IsTrue(e1.EqualsTo(e2));
+ }
+
+ public void Eq_complex_lambda_round_trip()
+ {
+ var expr = Lambda>(
+ MemberInit(
+ New(CtorOfA, New(CtorOfB)),
+ Bind(PropAProp, New(CtorOfB))),
+ ParameterOf