diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
index ad07b3fe..35c8b643 100644
--- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
+++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
@@ -61,6 +61,7 @@ public struct ExprNode
private const uint MetaKeepWithoutNext = 0xFFFF0000u;
// _data layout: bits [31:16]=ChildCount | [15:0]=ChildIdx (or full uint for inline constants)
private const int DataCountShift = 16;
+ private const uint DataKeepWithoutChildIdx = 0xFFFF0000u;
private const uint DataIdxMask = 0xFFFFu;
private const int FlagsShift = 4;
private const uint KindMask = 0x0Fu;
@@ -106,6 +107,7 @@ public struct ExprNode
internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags = 0, int childIdx = 0, int childCount = 0, int nextIdx = 0)
{
+ Debug.Assert(!RequiresInlineConstantStorage(type, obj, nodeType));
Type = type;
Obj = obj;
var tag = (byte)((flags << FlagsShift) | (byte)kind);
@@ -136,9 +138,39 @@ internal void SetChildInfo(int childIdx, int childCount) =>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool IsExpression() => Kind == ExprNodeKind.Expression;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static bool RequiresInlineConstantStorage(Type type, object obj, ExpressionType nodeType)
+ {
+ if (nodeType != ExpressionType.Constant || obj == null || ReferenceEquals(obj, InlineValueMarker))
+ return false;
+
+ return type.IsEnum
+ ? IsSmallPrimitive(Type.GetTypeCode(Enum.GetUnderlyingType(type)))
+ : type.IsPrimitive && IsSmallPrimitive(Type.GetTypeCode(type));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsSmallPrimitive(TypeCode tc) =>
+ tc == TypeCode.Boolean || tc == TypeCode.Byte || tc == TypeCode.SByte ||
+ tc == TypeCode.Char || tc == TypeCode.Int16 || tc == TypeCode.UInt16 ||
+ tc == TypeCode.Int32 || tc == TypeCode.UInt32 || tc == TypeCode.Single;
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool HasFlag(byte flag) => (Flags & flag) != 0;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool HasSameHeaderExceptNext(ref ExprNode other) =>
+ Type == other.Type && (_meta & MetaKeepWithoutNext) == (other._meta & MetaKeepWithoutNext);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool HasSameShapeExceptLinks(ref ExprNode other) =>
+ HasSameHeaderExceptNext(ref other) &&
+ (_data & DataKeepWithoutChildIdx) == (other._data & DataKeepWithoutChildIdx);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool HasSameShapeExceptNext(ref ExprNode other) =>
+ HasSameHeaderExceptNext(ref other) && _data == other._data;
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool ShouldCloneWhenLinked() =>
ReferenceEquals(Obj, InlineValueMarker) ||
@@ -173,7 +205,7 @@ public LambdaClosureParameterUsage(ushort lambdaIdx, ushort parameterIdx, ushort
}
/// Stores an expression tree as flat nodes plus separate closure constants.
-public struct ExprTree
+public struct ExprTree : IEquatable
{
private static readonly object ClosureConstantMarker = new();
private const byte ParameterByRefFlag = 1;
@@ -274,7 +306,7 @@ public int Constant(object value, Type type)
/// Adds an constant node.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int ConstantInt(int value) => AddRawExpressionNode(typeof(int), value, ExpressionType.Constant);
+ public int ConstantInt(int value) => AddInlineConstantNode(typeof(int), unchecked((uint)value));
/// Adds a typed constant node.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -709,6 +741,29 @@ public SysExpr ToExpression() =>
[RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)]
public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression());
+ /// Structurally compares two flat expression trees.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(ExprTree other) =>
+ new StructuralComparer().Eq(ref this, ref other);
+
+ /// Structurally compares this tree with another object.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj) =>
+ obj is ExprTree other && Equals(other);
+
+ /// Computes a content-addressable hash for the flat expression tree.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override int GetHashCode() =>
+ new StructuralComparer().Hash(ref this);
+
+ /// Determines whether two flat expression trees are structurally equal.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(ExprTree left, ExprTree right) => left.Equals(right);
+
+ /// Determines whether two flat expression trees are not structurally equal.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(ExprTree left, ExprTree right) => !left.Equals(right);
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int child) =>
AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, CloneChild(child));
@@ -1606,6 +1661,427 @@ private static bool Contains(ref SmallList
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ushort ToStoredUShortIdx(int idx) => checked((ushort)idx);
+ private struct StructuralComparer
+ {
+ private SmallList, NoArrayPool> _xParameterIds, _yParameterIds;
+ private SmallList, NoArrayPool> _xLabelIds, _yLabelIds;
+ private SmallList, NoArrayPool> _eqFrames;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Eq(ref ExprTree xTree, ref ExprTree yTree)
+ {
+ if (xTree.Nodes.Count == 0 || yTree.Nodes.Count == 0)
+ return xTree.Nodes.Count == yTree.Nodes.Count;
+
+ var xIdx = xTree.RootIdx;
+ var yIdx = yTree.RootIdx;
+ var remainingSiblings = 0;
+ while (true)
+ {
+ ref var x = ref xTree.Nodes.GetSurePresentRef(xIdx);
+ ref var y = ref yTree.Nodes.GetSurePresentRef(yIdx);
+ if (x.Kind == ExprNodeKind.UInt16Pair)
+ {
+ if (!x.HasSameShapeExceptNext(ref y))
+ return false;
+ }
+ else if (x.NodeType == ExpressionType.Constant)
+ {
+ if (!x.HasSameHeaderExceptNext(ref y))
+ return false;
+ }
+ else if (!x.HasSameShapeExceptLinks(ref y))
+ return false;
+
+ var descendX = 0;
+ var descendY = 0;
+ var descendChildCount = 0;
+ var restoreXParameterCount = -1;
+ var restoreYParameterCount = -1;
+
+ if (x.Kind != ExprNodeKind.UInt16Pair)
+ {
+ if (x.Kind == ExprNodeKind.LabelTarget)
+ {
+ if (!EqLabelTarget(ref x, ref y))
+ return false;
+ }
+ else if (x.Kind == ExprNodeKind.CatchBlock)
+ {
+ restoreXParameterCount = _xParameterIds.Count;
+ restoreYParameterCount = _yParameterIds.Count;
+ descendX = x.ChildIdx;
+ descendY = y.ChildIdx;
+ descendChildCount = x.ChildCount - (x.HasFlag(CatchHasVariableFlag) ? 1 : 0);
+ if (x.HasFlag(CatchHasVariableFlag))
+ {
+ ref var xv = ref xTree.Nodes.GetSurePresentRef(descendX);
+ ref var yv = ref yTree.Nodes.GetSurePresentRef(descendY);
+ if (!AreEquivalentParameterDeclarations(ref xv, ref yv))
+ return false;
+ _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx));
+ _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx));
+ descendX = xv.NextIdx;
+ descendY = yv.NextIdx;
+ }
+ }
+ else
+ {
+ switch (x.NodeType)
+ {
+ case ExpressionType.Parameter:
+ if (!EqParameter(ref x, ref y))
+ return false;
+ break;
+
+ case ExpressionType.Constant:
+ if (!AreConstantsEqual(ref xTree, ref x, ref yTree, ref y))
+ return false;
+ break;
+
+ case ExpressionType.Lambda:
+ if (x.ChildCount == 0)
+ return false;
+
+ restoreXParameterCount = _xParameterIds.Count;
+ restoreYParameterCount = _yParameterIds.Count;
+ descendX = x.ChildIdx;
+ descendY = y.ChildIdx;
+ descendChildCount = 1;
+ var xParameterIdx = xTree.Nodes.GetSurePresentRef(descendX).NextIdx;
+ var yParameterIdx = yTree.Nodes.GetSurePresentRef(descendY).NextIdx;
+ for (var i = 1; i < x.ChildCount; ++i)
+ {
+ ref var xp = ref xTree.Nodes.GetSurePresentRef(xParameterIdx);
+ ref var yp = ref yTree.Nodes.GetSurePresentRef(yParameterIdx);
+ if (!AreEquivalentParameterDeclarations(ref xp, ref yp))
+ return false;
+ _xParameterIds.Add(ToStoredUShortIdx(xp.ChildIdx));
+ _yParameterIds.Add(ToStoredUShortIdx(yp.ChildIdx));
+ xParameterIdx = xp.NextIdx;
+ yParameterIdx = yp.NextIdx;
+ }
+ break;
+
+ case ExpressionType.Block:
+ if (x.ChildCount == 0)
+ return false;
+
+ restoreXParameterCount = _xParameterIds.Count;
+ restoreYParameterCount = _yParameterIds.Count;
+ descendX = x.ChildIdx;
+ descendY = y.ChildIdx;
+ descendChildCount = 1;
+ if (x.ChildCount == 2)
+ {
+ ref var xVariables = ref xTree.Nodes.GetSurePresentRef(descendX);
+ ref var yVariables = ref yTree.Nodes.GetSurePresentRef(descendY);
+ if (xVariables.Kind != ExprNodeKind.ChildList || yVariables.Kind != ExprNodeKind.ChildList ||
+ xVariables.ChildCount != yVariables.ChildCount)
+ return false;
+
+ var xVariableIdx = xVariables.ChildIdx;
+ var yVariableIdx = yVariables.ChildIdx;
+ for (var i = 0; i < xVariables.ChildCount; ++i)
+ {
+ ref var xv = ref xTree.Nodes.GetSurePresentRef(xVariableIdx);
+ ref var yv = ref yTree.Nodes.GetSurePresentRef(yVariableIdx);
+ if (!AreEquivalentParameterDeclarations(ref xv, ref yv))
+ return false;
+ _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx));
+ _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx));
+ xVariableIdx = xv.NextIdx;
+ yVariableIdx = yv.NextIdx;
+ }
+
+ descendX = xVariables.NextIdx;
+ descendY = yVariables.NextIdx;
+ }
+ break;
+
+ default:
+ if (!EqObj(ref x, ref y))
+ return false;
+ if (x.ChildCount != 0)
+ {
+ descendX = x.ChildIdx;
+ descendY = y.ChildIdx;
+ descendChildCount = x.ChildCount;
+ }
+ break;
+ }
+ }
+ }
+
+ if (descendChildCount != 0)
+ {
+ _eqFrames.Add(new TraversalFrame(x.NextIdx, y.NextIdx, remainingSiblings, restoreXParameterCount, restoreYParameterCount));
+ xIdx = descendX;
+ yIdx = descendY;
+ remainingSiblings = descendChildCount - 1;
+ continue;
+ }
+
+ var advanced = false;
+ while (true)
+ {
+ if (remainingSiblings != 0)
+ {
+ xIdx = x.NextIdx;
+ yIdx = y.NextIdx;
+ remainingSiblings--;
+ advanced = true;
+ break;
+ }
+
+ if (_eqFrames.Count == 0)
+ return true;
+
+ var frame = _eqFrames[_eqFrames.Count - 1];
+ _eqFrames.Count--;
+ if (frame.XParameterCount >= 0)
+ _xParameterIds.Count = frame.XParameterCount;
+ if (frame.YParameterCount >= 0)
+ _yParameterIds.Count = frame.YParameterCount;
+ if (frame.RemainingSiblingsAfterNode != 0)
+ {
+ xIdx = frame.XNextIdx;
+ yIdx = frame.YNextIdx;
+ remainingSiblings = frame.RemainingSiblingsAfterNode - 1;
+ advanced = true;
+ break;
+ }
+ }
+ if (advanced)
+ continue;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int Hash(ref ExprTree tree) =>
+ tree.Nodes.Count == 0 ? 0 : HashNode(ref tree, tree.RootIdx);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int Combine(int h1, int h2) =>
+ unchecked(h1 ^ (h2 + (int)0x9e3779b9 + (h1 << 6) + (h1 >> 2)));
+
+ private bool EqParameter(ref ExprNode x, ref ExprNode y)
+ {
+ var xId = ToStoredUShortIdx(x.ChildIdx);
+ for (var i = 0; i < _xParameterIds.Count; ++i)
+ if (_xParameterIds[i] == xId)
+ return _yParameterIds[i] == ToStoredUShortIdx(y.ChildIdx);
+
+ return x.HasFlag(ParameterByRefFlag) == y.HasFlag(ParameterByRefFlag) &&
+ Equals(x.Obj, y.Obj);
+ }
+
+ private bool EqLabelTarget(ref ExprNode x, ref ExprNode y)
+ {
+ var xId = ToStoredUShortIdx(x.ChildIdx);
+ for (var i = 0; i < _xLabelIds.Count; ++i)
+ if (_xLabelIds[i] == xId)
+ return _yLabelIds[i] == ToStoredUShortIdx(y.ChildIdx);
+
+ _xLabelIds.Add(xId);
+ _yLabelIds.Add(ToStoredUShortIdx(y.ChildIdx));
+ return Equals(x.Obj, y.Obj);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool AreEquivalentParameterDeclarations(ref ExprNode x, ref ExprNode y) =>
+ x.NodeType == ExpressionType.Parameter &&
+ y.NodeType == ExpressionType.Parameter &&
+ x.HasSameShapeExceptLinks(ref y);
+
+ private static bool EqObj(ref ExprNode x, ref ExprNode y) =>
+ ReferenceEquals(x.Obj, y.Obj) || Equals(x.Obj, y.Obj);
+
+ private int HashNode(ref ExprTree tree, int idx)
+ {
+ ref var node = ref tree.Nodes.GetSurePresentRef(idx);
+ if (node.Kind == ExprNodeKind.LabelTarget)
+ return Combine(Combine((int)node.Kind, node.Type?.GetHashCode() ?? 0), node.Obj?.GetHashCode() ?? 0);
+
+ if (node.Kind == ExprNodeKind.CatchBlock)
+ return HashCatchBlock(ref tree, idx, ref node);
+
+ if (node.Kind == ExprNodeKind.UInt16Pair)
+ return Combine(Combine((int)node.Kind, node.ChildIdx), node.ChildCount);
+
+ var h = Combine(Combine((int)node.Kind, (int)node.NodeType), node.Type?.GetHashCode() ?? 0);
+ h = Combine(h, node.Flags);
+
+ switch (node.NodeType)
+ {
+ case ExpressionType.Parameter:
+ {
+ var id = ToStoredUShortIdx(node.ChildIdx);
+ for (var i = 0; i < _xParameterIds.Count; ++i)
+ if (_xParameterIds[i] == id)
+ return Combine(h, i);
+ return Combine(h, node.Obj?.GetHashCode() ?? 0);
+ }
+
+ case ExpressionType.Constant:
+ return Combine(h, GetConstantHashCode(ref tree, ref node));
+
+ case ExpressionType.Lambda:
+ return HashLambda(ref tree, idx, h);
+
+ case ExpressionType.Block:
+ return HashBlock(ref tree, idx, h);
+ }
+
+ h = Combine(h, node.Obj?.GetHashCode() ?? 0);
+ var childIdx = node.ChildIdx;
+ for (var i = 0; i < node.ChildCount; ++i)
+ {
+ h = Combine(h, HashNode(ref tree, childIdx));
+ childIdx = tree.Nodes.GetSurePresentRef(childIdx).NextIdx;
+ }
+ return h;
+ }
+
+ private int HashLambda(ref ExprTree tree, int idx, int h)
+ {
+ var scopeCount = _xParameterIds.Count;
+ ref var node = ref tree.Nodes.GetSurePresentRef(idx);
+ var bodyIdx = node.ChildIdx;
+ var parameterIdx = tree.Nodes.GetSurePresentRef(bodyIdx).NextIdx;
+ for (var i = 1; i < node.ChildCount; ++i)
+ {
+ ref var parameter = ref tree.Nodes.GetSurePresentRef(parameterIdx);
+ _xParameterIds.Add(ToStoredUShortIdx(parameter.ChildIdx));
+ h = Combine(h, Combine(parameter.Type?.GetHashCode() ?? 0, parameter.HasFlag(ParameterByRefFlag) ? 1 : 0));
+ parameterIdx = parameter.NextIdx;
+ }
+
+ h = Combine(h, HashNode(ref tree, bodyIdx));
+ _xParameterIds.Count = scopeCount;
+ return h;
+ }
+
+ private int HashBlock(ref ExprTree tree, int idx, int h)
+ {
+ var scopeCount = _xParameterIds.Count;
+ ref var node = ref tree.Nodes.GetSurePresentRef(idx);
+ var bodyListIdx = node.ChildIdx;
+ if (node.ChildCount == 2)
+ {
+ ref var variables = ref tree.Nodes.GetSurePresentRef(bodyListIdx);
+ var variableIdx = variables.ChildIdx;
+ for (var i = 0; i < variables.ChildCount; ++i)
+ {
+ ref var variable = ref tree.Nodes.GetSurePresentRef(variableIdx);
+ _xParameterIds.Add(ToStoredUShortIdx(variable.ChildIdx));
+ h = Combine(h, Combine(variable.Type?.GetHashCode() ?? 0, variable.HasFlag(ParameterByRefFlag) ? 1 : 0));
+ variableIdx = variable.NextIdx;
+ }
+ bodyListIdx = variables.NextIdx;
+ }
+
+ h = Combine(h, HashNode(ref tree, bodyListIdx));
+ _xParameterIds.Count = scopeCount;
+ return h;
+ }
+
+ private int HashCatchBlock(ref ExprTree tree, int idx, ref ExprNode node)
+ {
+ var h = Combine(Combine((int)node.Kind, node.Type?.GetHashCode() ?? 0), node.Flags);
+ var scopeCount = _xParameterIds.Count;
+ var childIdx = 0;
+ var catchChildIdx = node.ChildIdx;
+ if (node.HasFlag(CatchHasVariableFlag))
+ {
+ ref var variable = ref tree.Nodes.GetSurePresentRef(catchChildIdx);
+ _xParameterIds.Add(ToStoredUShortIdx(variable.ChildIdx));
+ h = Combine(h, Combine(variable.Type?.GetHashCode() ?? 0, variable.HasFlag(ParameterByRefFlag) ? 1 : 0));
+ catchChildIdx = variable.NextIdx;
+ childIdx++;
+ }
+
+ h = Combine(h, HashNode(ref tree, catchChildIdx));
+ catchChildIdx = tree.Nodes.GetSurePresentRef(catchChildIdx).NextIdx;
+ childIdx++;
+ if (node.HasFlag(CatchHasFilterFlag))
+ h = Combine(h, HashNode(ref tree, catchChildIdx));
+
+ _xParameterIds.Count = scopeCount;
+ return h;
+ }
+
+ private static int GetConstantHashCode(ref ExprTree tree, ref ExprNode node)
+ {
+ if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker))
+ return GetInlineConstantHashCode(node.Type, node.InlineValue);
+
+ Debug.Assert(!ExprNode.RequiresInlineConstantStorage(node.Type, node.Obj, node.NodeType));
+ return GetStoredConstantValue(ref tree, ref node)?.GetHashCode() ?? 0;
+ }
+
+ private static bool AreConstantsEqual(ref ExprTree xTree, ref ExprNode x, ref ExprTree yTree, ref ExprNode y)
+ {
+ var xInline = ReferenceEquals(x.Obj, ExprNode.InlineValueMarker);
+ var yInline = ReferenceEquals(y.Obj, ExprNode.InlineValueMarker);
+ Debug.Assert(xInline == yInline);
+ if (xInline != yInline)
+ return false;
+
+ if (!xInline)
+ {
+ Debug.Assert(!ExprNode.RequiresInlineConstantStorage(x.Type, x.Obj, x.NodeType));
+ var xObj = GetStoredConstantValue(ref xTree, ref x);
+ var yObj = GetStoredConstantValue(ref yTree, ref y);
+ return xObj?.Equals(yObj) ?? yObj == null;
+ }
+
+ if (x.Type.IsEnum)
+ return x.InlineValue == y.InlineValue;
+
+ var typeCode = Type.GetTypeCode(x.Type);
+ Debug.Assert(IsSmallPrimitive(typeCode));
+ return typeCode != TypeCode.Single
+ ? x.InlineValue == y.InlineValue
+ : FloatBits.ToFloat(x.InlineValue).Equals(FloatBits.ToFloat(y.InlineValue));
+ }
+
+ private static object GetStoredConstantValue(ref ExprTree tree, ref ExprNode node) =>
+ ReferenceEquals(node.Obj, ClosureConstantMarker) ? tree.ClosureConstants[node.ChildIdx] : node.Obj;
+
+ private static int GetInlineConstantHashCode(Type type, uint data)
+ {
+ if (!type.IsEnum)
+ {
+ var typeCode = Type.GetTypeCode(type);
+ Debug.Assert(IsSmallPrimitive(typeCode));
+ if (typeCode == TypeCode.Single)
+ return FloatBits.ToFloat(data).GetHashCode();
+ }
+
+ return data.GetHashCode();
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct TraversalFrame
+ {
+ public readonly int RemainingSiblingsAfterNode;
+ public readonly int XParameterCount;
+ public readonly int YParameterCount;
+ public readonly ushort XNextIdx;
+ public readonly ushort YNextIdx;
+
+ public TraversalFrame(int xNextIdx, int yNextIdx, int remainingSiblingsAfterNode, int xParameterCount, int yParameterCount)
+ {
+ RemainingSiblingsAfterNode = remainingSiblingsAfterNode;
+ XParameterCount = xParameterCount;
+ YParameterCount = yParameterCount;
+ XNextIdx = checked((ushort)xNextIdx);
+ YNextIdx = checked((ushort)yNextIdx);
+ }
+ }
+ }
+
/// Reconstructs System.Linq nodes from the flat representation while reusing parameter and label identities.
private struct Reader
{
diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs
index 3e3ff694..98084bd8 100644
--- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs
+++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs
@@ -55,7 +55,11 @@ public int Run()
Flat_blocks_with_variables_tracked_from_expression_conversion();
Flat_goto_and_label_nodes_tracked_from_expression_conversion();
Flat_try_catch_nodes_tracked_from_expression_conversion();
- return 38;
+ Flat_equal_lambdas_with_different_parameter_names_are_structurally_equal_and_hash_equal();
+ Flat_equal_nested_lambdas_with_captures_are_structurally_equal_and_hash_equal();
+ Flat_standalone_parameters_use_name_in_structural_equality();
+ Flat_structural_hash_supports_dictionary_lookup();
+ return 42;
}
@@ -1023,5 +1027,56 @@ public void Flat_try_catch_nodes_tracked_from_expression_conversion()
Asserts.AreEqual(1, fe.TryCatchNodes.Count);
}
+
+ public void Flat_equal_lambdas_with_different_parameter_names_are_structurally_equal_and_hash_equal()
+ {
+ var x = Parameter(typeof(int), "x");
+ var left = Lambda>(Add(x, Constant(1)), x).ToFlatExpression();
+
+ var y = Parameter(typeof(int), "y");
+ var right = Lambda>(Add(y, Constant(1)), y).ToFlatExpression();
+
+ Asserts.IsTrue(left.Equals(right));
+ Asserts.IsTrue(left == right);
+ Asserts.AreEqual(left.GetHashCode(), right.GetHashCode());
+ }
+
+ public void Flat_equal_nested_lambdas_with_captures_are_structurally_equal_and_hash_equal()
+ {
+ var x = Parameter(typeof(int), "x");
+ var left = Lambda>>(
+ Lambda>(Add(x, Constant(1))),
+ x).ToFlatExpression();
+
+ var y = Parameter(typeof(int), "value");
+ var right = Lambda>>(
+ Lambda>(Add(y, Constant(1))),
+ y).ToFlatExpression();
+
+ Asserts.IsTrue(left.Equals(right));
+ Asserts.AreEqual(left.GetHashCode(), right.GetHashCode());
+ }
+
+ public void Flat_standalone_parameters_use_name_in_structural_equality()
+ {
+ var left = Parameter(typeof(int), "x").ToFlatExpression();
+ var right = Parameter(typeof(int), "y").ToFlatExpression();
+
+ Asserts.IsFalse(left.Equals(right));
+ }
+
+ public void Flat_structural_hash_supports_dictionary_lookup()
+ {
+ var x = Parameter(typeof(int), "x");
+ var key = Lambda>(Add(x, Constant(1)), x).ToFlatExpression();
+ var dict = new Dictionary { [key] = "found" };
+
+ var lookup = default(ExprTree);
+ var y = lookup.ParameterOf("arg");
+ lookup.RootIdx = lookup.Lambda>(lookup.Add(y, lookup.ConstantInt(1)), y);
+
+ Asserts.IsTrue(dict.TryGetValue(lookup, out var value));
+ Asserts.AreEqual("found", value);
+ }
}
}