From c0eb736f927831c7614d140eff1beb9612c634cc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 19 Apr 2026 22:05:02 +0000
Subject: [PATCH 1/3] Initial plan
From 4d79f4f121c2bfc8a258752a0bdc2255a9dbef82 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 19 Apr 2026 22:20:21 +0000
Subject: [PATCH 2/3] Add FlatExpression.cs with ExprNode/ExprTree structs and
FlatExpressionTests
Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/106c0b3a-7cd7-4eb7-a391-c8ac412e1222
Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
---
.../FlatExpression.cs | 1017 +++++++++++++++++
.../FlatExpressionTests.cs | 252 ++++
.../Program.cs | 1 +
3 files changed, 1270 insertions(+)
create mode 100644 src/FastExpressionCompiler.LightExpression/FlatExpression.cs
create mode 100644 test/FastExpressionCompiler.LightExpression.UnitTests/FlatExpressionTests.cs
diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
new file mode 100644
index 00000000..cb2d7b91
--- /dev/null
+++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
@@ -0,0 +1,1017 @@
+#nullable disable
+#pragma warning disable CS1591
+
+namespace FastExpressionCompiler.LightExpression;
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using FastExpressionCompiler.LightExpression.ImTools;
+
+using LE = FastExpressionCompiler.LightExpression;
+
+///
+/// A single node in the flat expression tree. Packs all node metadata into a 64-bit
+/// _data field and two object references (Type, Obj).
+///
+/// _data layout (high-to-low bits):
+///
+/// - bits 63–56: (8 bits)
+/// - bits 55–48: Extra – node-type-specific auxiliary byte (8 bits)
+/// - bits 47–32: unused / reserved (16 bits)
+/// - bits 31–16: ChildCount – number of children (16 bits)
+/// - bits 15– 0: FirstChildPoolIdx – offset into child pool (16 bits)
+///
+///
+///
+[StructLayout(LayoutKind.Sequential)]
+public struct ExprNode
+{
+ /// Sentinel value meaning "no index".
+ public const ushort NoIdx = 0xFFFF;
+
+ /// The CLR type of the expression result.
+ public Type Type;
+
+ ///
+ /// Node-type-specific payload:
+ ///
+ /// - Constant – the constant value (boxed)
+ /// - Parameter – the parameter name (string, may be null)
+ /// - New – (null for struct default-ctor)
+ /// - Call –
+ /// - Field –
+ /// - Property (MemberAccess) –
+ /// - Index – (null for array element access)
+ /// - Binary – or null
+ /// - Unary – or null
+ /// - Conditional – explicit result or null (derive from IfTrue)
+ /// - Loop – LabelTarget[] {breakLabel, continueLabel}, either may be null
+ /// - Try – [] (may be null)
+ /// - Label –
+ /// - Goto –
+ /// - Switch – object[] {[], comparison (may be null)}
+ /// - TypeBinary – the operand to test against
+ /// - MemberInit – []
+ /// - ListInit – []
+ /// - NewArrayInit/NewArrayBounds – null (element type is derived from Type)
+ ///
+ ///
+ public object Obj;
+
+ private ulong _data;
+
+ /// The of this node.
+ public ExpressionType NodeType
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => (ExpressionType)((_data >> 56) & 0xFF);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => _data = (_data & ~(0xFFUL << 56)) | ((ulong)(byte)value << 56);
+ }
+
+ ///
+ /// Node-type-specific auxiliary byte:
+ ///
+ /// - Parameter – 1 if by-ref, 0 otherwise
+ /// - Lambda – number of parameters (the body is the last child)
+ /// - Block – number of variables (the rest of children are expressions)
+ /// - Goto – value
+ /// - Try – variant: 0=Catch, 1=Finally, 2=CatchFinally, 3=Fault
+ /// - Call – 1 if first child is the instance, 0 if fully static
+ /// - Index – 1 if first child is the instance (indexer or array), always 1 for this node type
+ ///
+ ///
+ internal byte Extra
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => (byte)((_data >> 48) & 0xFF);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => _data = (_data & ~(0xFFUL << 48)) | ((ulong)value << 48);
+ }
+
+ /// Number of child nodes.
+ public ushort ChildCount
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => (ushort)((_data >> 16) & 0xFFFF);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => _data = (_data & ~(0xFFFFUL << 16)) | ((ulong)value << 16);
+ }
+
+ ///
+ /// Offset into where the children of this node start.
+ /// means no children.
+ ///
+ public ushort FirstChildPoolIdx
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => (ushort)(_data & 0xFFFF);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => _data = (_data & ~0xFFFFUL) | (ulong)value;
+ }
+
+ internal void SetData(ExpressionType nodeType, byte extra, ushort childCount, ushort firstChildPoolIdx)
+ {
+ _data = ((ulong)(byte)nodeType << 56) |
+ ((ulong)extra << 48) |
+ ((ulong)childCount << 16) |
+ (ulong)firstChildPoolIdx;
+ }
+}
+
+///
+/// Data-oriented, flat representation of an expression tree.
+///
+/// Small trees (up to 16 nodes, 16 child-pool slots) stay fully on the stack.
+/// Nodes are appended to ; child indices are appended to
+/// using a non-intrusive approach that allows the same node to appear as a child of multiple parents.
+///
+///
+/// Factory methods return a node index.
+/// Call to convert back to a LightExpression for compilation.
+///
+///
+public struct ExprTree
+{
+ /// All expression nodes; first 16 slots live on the stack.
+ public SmallList, NoArrayPool> Nodes;
+
+ ///
+ /// Non-intrusive child-index pool. Each parent node holds a contiguous slice
+ /// [FirstChildPoolIdx .. FirstChildPoolIdx + ChildCount - 1].
+ /// First 16 slots live on the stack.
+ ///
+ public SmallList, NoArrayPool> ChildPool;
+
+ // ── private helpers ───────────────────────────────────────────────────────
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ushort AddNode(ExpressionType nodeType, Type type, object obj,
+ byte extra, ushort childCount, ushort firstChildPoolIdx)
+ {
+ var node = new ExprNode { Type = type, Obj = obj };
+ node.SetData(nodeType, extra, childCount, firstChildPoolIdx);
+ return (ushort)Nodes.Add(in node);
+ }
+
+ /// Appends to the child pool and returns the first pool index.
+ private ushort AppendChildren(ushort[] children)
+ {
+ var start = (ushort)ChildPool.Count;
+ foreach (var c in children)
+ ChildPool.Add(c);
+ return start;
+ }
+
+ private ushort AddNodeWithChildren(ExpressionType nodeType, Type type, object obj, byte extra, ushort[] children)
+ {
+ ushort poolStart = ExprNode.NoIdx;
+ if (children != null && children.Length > 0)
+ poolStart = AppendChildren(children);
+ return AddNode(nodeType, type, obj, extra, (ushort)(children?.Length ?? 0), poolStart);
+ }
+
+ // ── Constant ──────────────────────────────────────────────────────────────
+
+ /// Creates a Constant node.
+ public ushort Constant(object value, Type type = null)
+ {
+ if (value == null)
+ return AddNode(ExpressionType.Constant, type ?? typeof(object), null, 0, 0, ExprNode.NoIdx);
+ type ??= value.GetType();
+ return AddNode(ExpressionType.Constant, type, value, 0, 0, ExprNode.NoIdx);
+ }
+
+ /// Creates a Constant node with a strongly typed value.
+ public ushort Constant(T value) =>
+ AddNode(ExpressionType.Constant, typeof(T), value, 0, 0, ExprNode.NoIdx);
+
+ // ── Parameter / Variable ──────────────────────────────────────────────────
+
+ /// Creates a Parameter/Variable node.
+ public ushort Parameter(Type type, string name = null)
+ {
+ if (type.IsByRef)
+ return AddNode(ExpressionType.Parameter, type.GetElementType(), name, extra: 1, 0, ExprNode.NoIdx);
+ return AddNode(ExpressionType.Parameter, type, name, 0, 0, ExprNode.NoIdx);
+ }
+
+ /// Creates a strongly-typed Parameter/Variable node.
+ public ushort Parameter(string name = null) =>
+ AddNode(ExpressionType.Parameter, typeof(T), name, 0, 0, ExprNode.NoIdx);
+
+ /// Alias for .
+ public ushort Variable(Type type, string name = null) => Parameter(type, name);
+
+ /// Alias for .
+ public ushort Variable(string name = null) => Parameter(name);
+
+ // ── New ───────────────────────────────────────────────────────────────────
+
+ /// Creates a New node for a value type default constructor.
+ public ushort New(Type valueType) =>
+ AddNode(ExpressionType.New, valueType, null, 0, 0, ExprNode.NoIdx);
+
+ /// Creates a New node with no arguments.
+ public ushort New(ConstructorInfo ctor) =>
+ AddNode(ExpressionType.New, ctor.DeclaringType, ctor, 0, 0, ExprNode.NoIdx);
+
+ /// Creates a New node with arguments.
+ public ushort New(ConstructorInfo ctor, params ushort[] args) =>
+ AddNodeWithChildren(ExpressionType.New, ctor.DeclaringType, ctor, 0, args);
+
+ // ── Call ──────────────────────────────────────────────────────────────────
+
+ /// Creates a static method call node.
+ public ushort Call(MethodInfo method, params ushort[] args) =>
+ AddNodeWithChildren(ExpressionType.Call, method.ReturnType, method, extra: 0, args);
+
+ /// Creates an instance method call node; is the first child.
+ public ushort Call(ushort instance, MethodInfo method, params ushort[] args)
+ {
+ var allChildren = new ushort[1 + (args?.Length ?? 0)];
+ allChildren[0] = instance;
+ if (args != null)
+ for (var i = 0; i < args.Length; i++)
+ allChildren[i + 1] = args[i];
+ return AddNodeWithChildren(ExpressionType.Call, method.ReturnType, method, extra: 1, allChildren);
+ }
+
+ // ── Field ─────────────────────────────────────────────────────────────────
+
+ /// Creates a static field access node.
+ public ushort Field(FieldInfo field) =>
+ AddNode(ExpressionType.MemberAccess, field.FieldType, field, 0, 0, ExprNode.NoIdx);
+
+ /// Creates an instance field access node.
+ public ushort Field(ushort instance, FieldInfo field) =>
+ AddNodeWithChildren(ExpressionType.MemberAccess, field.FieldType, field, extra: 1, new[] { instance });
+
+ // ── Property ─────────────────────────────────────────────────────────────
+
+ /// Creates a static property access node.
+ public ushort Property(PropertyInfo property) =>
+ AddNode(ExpressionType.MemberAccess, property.PropertyType, property, 0, 0, ExprNode.NoIdx);
+
+ /// Creates an instance property access node.
+ public ushort Property(ushort instance, PropertyInfo property) =>
+ AddNodeWithChildren(ExpressionType.MemberAccess, property.PropertyType, property, extra: 1, new[] { instance });
+
+ // ── Index (array access / indexer) ────────────────────────────────────────
+
+ /// Creates an array element access node.
+ public ushort ArrayAccess(ushort array, params ushort[] indexes)
+ {
+ var elemType = Nodes.GetSurePresentRef(array).Type.GetElementType();
+ var all = new ushort[1 + indexes.Length];
+ all[0] = array;
+ for (var i = 0; i < indexes.Length; i++) all[i + 1] = indexes[i];
+ return AddNodeWithChildren(ExpressionType.Index, elemType, null, extra: 0, all);
+ }
+
+ /// Creates a property indexer node.
+ public ushort Index(ushort instance, PropertyInfo indexer, params ushort[] args)
+ {
+ var all = new ushort[1 + args.Length];
+ all[0] = instance;
+ for (var i = 0; i < args.Length; i++) all[i + 1] = args[i];
+ return AddNodeWithChildren(ExpressionType.Index, indexer.PropertyType, indexer, extra: 0, all);
+ }
+
+ // ── Binary ────────────────────────────────────────────────────────────────
+
+ /// Creates a binary expression node. is inferred from left if null.
+ public ushort Binary(ExpressionType op, ushort left, ushort right, Type type = null, MethodInfo method = null)
+ {
+ type ??= Nodes.GetSurePresentRef(left).Type;
+ return AddNodeWithChildren(op, type, method, 0, new[] { left, right });
+ }
+
+ /// Creates an Assign node.
+ public ushort Assign(ushort left, ushort right) =>
+ Binary(ExpressionType.Assign, left, right);
+
+ /// Creates an Add node.
+ public ushort Add(ushort left, ushort right, MethodInfo method = null) =>
+ Binary(ExpressionType.Add, left, right, null, method);
+
+ /// Creates a Subtract node.
+ public ushort Subtract(ushort left, ushort right, MethodInfo method = null) =>
+ Binary(ExpressionType.Subtract, left, right, null, method);
+
+ /// Creates a Multiply node.
+ public ushort Multiply(ushort left, ushort right, MethodInfo method = null) =>
+ Binary(ExpressionType.Multiply, left, right, null, method);
+
+ /// Creates a Divide node.
+ public ushort Divide(ushort left, ushort right, MethodInfo method = null) =>
+ Binary(ExpressionType.Divide, left, right, null, method);
+
+ /// Creates a Modulo node.
+ public ushort Modulo(ushort left, ushort right, MethodInfo method = null) =>
+ Binary(ExpressionType.Modulo, left, right, null, method);
+
+ /// Creates an Equal node.
+ public ushort Equal(ushort left, ushort right) =>
+ Binary(ExpressionType.Equal, left, right, typeof(bool));
+
+ /// Creates a NotEqual node.
+ public ushort NotEqual(ushort left, ushort right) =>
+ Binary(ExpressionType.NotEqual, left, right, typeof(bool));
+
+ /// Creates a LessThan node.
+ public ushort LessThan(ushort left, ushort right) =>
+ Binary(ExpressionType.LessThan, left, right, typeof(bool));
+
+ /// Creates a LessThanOrEqual node.
+ public ushort LessThanOrEqual(ushort left, ushort right) =>
+ Binary(ExpressionType.LessThanOrEqual, left, right, typeof(bool));
+
+ /// Creates a GreaterThan node.
+ public ushort GreaterThan(ushort left, ushort right) =>
+ Binary(ExpressionType.GreaterThan, left, right, typeof(bool));
+
+ /// Creates a GreaterThanOrEqual node.
+ public ushort GreaterThanOrEqual(ushort left, ushort right) =>
+ Binary(ExpressionType.GreaterThanOrEqual, left, right, typeof(bool));
+
+ /// Creates an AndAlso (short-circuit AND) node.
+ public ushort AndAlso(ushort left, ushort right) =>
+ Binary(ExpressionType.AndAlso, left, right, typeof(bool));
+
+ /// Creates an OrElse (short-circuit OR) node.
+ public ushort OrElse(ushort left, ushort right) =>
+ Binary(ExpressionType.OrElse, left, right, typeof(bool));
+
+ /// Creates an And (bitwise) node.
+ public ushort And(ushort left, ushort right, MethodInfo method = null) =>
+ Binary(ExpressionType.And, left, right, null, method);
+
+ /// Creates an Or (bitwise) node.
+ public ushort Or(ushort left, ushort right, MethodInfo method = null) =>
+ Binary(ExpressionType.Or, left, right, null, method);
+
+ /// Creates an ExclusiveOr node.
+ public ushort ExclusiveOr(ushort left, ushort right, MethodInfo method = null) =>
+ Binary(ExpressionType.ExclusiveOr, left, right, null, method);
+
+ /// Creates a Coalesce node.
+ public ushort Coalesce(ushort left, ushort right)
+ {
+ var leftType = Nodes.GetSurePresentRef(left).Type;
+ var rightType = Nodes.GetSurePresentRef(right).Type;
+ var resultType = leftType.IsGenericType && leftType.GetGenericTypeDefinition() == typeof(Nullable<>)
+ ? leftType.GetGenericArguments()[0]
+ : leftType;
+ if (!resultType.IsAssignableFrom(rightType))
+ resultType = rightType;
+ return Binary(ExpressionType.Coalesce, left, right, resultType);
+ }
+
+ /// Creates an ArrayIndex node.
+ public ushort ArrayIndex(ushort array, ushort index)
+ {
+ var elemType = Nodes.GetSurePresentRef(array).Type.GetElementType();
+ return Binary(ExpressionType.ArrayIndex, array, index, elemType);
+ }
+
+ // ── Unary ─────────────────────────────────────────────────────────────────
+
+ /// Creates a unary expression node.
+ public ushort Unary(ExpressionType op, ushort operand, Type type = null, MethodInfo method = null)
+ {
+ type ??= Nodes.GetSurePresentRef(operand).Type;
+ return AddNodeWithChildren(op, type, method, 0, new[] { operand });
+ }
+
+ /// Creates a Convert node.
+ public ushort Convert(ushort operand, Type type, MethodInfo method = null) =>
+ Unary(ExpressionType.Convert, operand, type, method);
+
+ /// Creates a Not node.
+ public ushort Not(ushort operand) => Unary(ExpressionType.Not, operand);
+
+ /// Creates a Negate node.
+ public ushort Negate(ushort operand) => Unary(ExpressionType.Negate, operand);
+
+ /// Creates an ArrayLength node.
+ public ushort ArrayLength(ushort array) => Unary(ExpressionType.ArrayLength, array, typeof(int));
+
+ /// Creates a TypeAs node.
+ public ushort TypeAs(ushort operand, Type type) => Unary(ExpressionType.TypeAs, operand, type);
+
+ /// Creates a Throw node.
+ public ushort Throw(ushort operand, Type type = null) =>
+ Unary(ExpressionType.Throw, operand, type ?? typeof(void));
+
+ /// Creates an Unbox node.
+ public ushort Unbox(ushort operand, Type type) => Unary(ExpressionType.Unbox, operand, type);
+
+ /// Creates a Quote node.
+ public ushort Quote(ushort operand) => Unary(ExpressionType.Quote, operand);
+
+ // ── Lambda ────────────────────────────────────────────────────────────────
+
+ ///
+ /// Creates a Lambda node. Extra = paramCount. children = params[0..n-1] + body.
+ ///
+ public ushort Lambda(Type delegateType, ushort body, params ushort[] parameters)
+ {
+ var paramCount = parameters?.Length ?? 0;
+ var all = new ushort[paramCount + 1];
+ if (parameters != null)
+ for (var i = 0; i < paramCount; i++) all[i] = parameters[i];
+ all[paramCount] = body;
+ return AddNodeWithChildren(ExpressionType.Lambda, delegateType, null, (byte)paramCount, all);
+ }
+
+ /// Creates a strongly-typed Lambda node.
+ public ushort Lambda(ushort body, params ushort[] parameters)
+ where TDelegate : System.Delegate =>
+ Lambda(typeof(TDelegate), body, parameters);
+
+ // ── Block ─────────────────────────────────────────────────────────────────
+
+ ///
+ /// Creates a Block node. Extra = varCount. children = vars[0..m-1] + exprs[0..k-1].
+ ///
+ public ushort Block(ushort[] variables, ushort[] expressions, Type type = null)
+ {
+ var varCount = variables?.Length ?? 0;
+ var exprCount = expressions?.Length ?? 0;
+ var all = new ushort[varCount + exprCount];
+ if (variables != null)
+ for (var i = 0; i < varCount; i++) all[i] = variables[i];
+ if (expressions != null)
+ for (var i = 0; i < exprCount; i++) all[varCount + i] = expressions[i];
+
+ if (exprCount == 0)
+ type ??= typeof(void);
+ else
+ type ??= Nodes.GetSurePresentRef(expressions[exprCount - 1]).Type;
+ return AddNodeWithChildren(ExpressionType.Block, type, null, (byte)varCount, all);
+ }
+
+ /// Creates a Block node with no variables.
+ public ushort Block(params ushort[] expressions) => Block(null, expressions);
+
+ /// Creates a Block node with no variables and an explicit result type.
+ public ushort Block(Type type, params ushort[] expressions) => Block(null, expressions, type);
+
+ // ── Conditional ───────────────────────────────────────────────────────────
+
+ /// Creates a Conditional (if-then-else) node.
+ public ushort Condition(ushort test, ushort ifTrue, ushort ifFalse, Type type = null)
+ {
+ type ??= Nodes.GetSurePresentRef(ifTrue).Type;
+ return AddNodeWithChildren(ExpressionType.Conditional, type, null, 0, new[] { test, ifTrue, ifFalse });
+ }
+
+ /// Creates an IfThen (void conditional) node.
+ public ushort IfThen(ushort test, ushort ifTrue) =>
+ AddNodeWithChildren(ExpressionType.Conditional, typeof(void), null, 0, new[] { test, ifTrue });
+
+ /// Creates an IfThenElse node (type derived from ifTrue).
+ public ushort IfThenElse(ushort test, ushort ifTrue, ushort ifFalse) =>
+ Condition(test, ifTrue, ifFalse);
+
+ // ── Loop ──────────────────────────────────────────────────────────────────
+
+ /// Creates a Loop node.
+ public ushort Loop(ushort body, LabelTarget breakLabel = null, LabelTarget continueLabel = null)
+ {
+ var loopType = breakLabel?.Type ?? typeof(void);
+ return AddNodeWithChildren(ExpressionType.Loop, loopType,
+ new LabelTarget[] { breakLabel, continueLabel }, 0, new[] { body });
+ }
+
+ // ── Try ───────────────────────────────────────────────────────────────────
+
+ // Extra: 0=Catch, 1=Finally, 2=CatchFinally, 3=Fault
+
+ /// Creates a TryCatch node.
+ public ushort TryCatch(ushort body, params CatchBlock[] handlers)
+ {
+ var bodyType = Nodes.GetSurePresentRef(body).Type;
+ return AddNodeWithChildren(ExpressionType.Try, bodyType, handlers, extra: 0, new[] { body });
+ }
+
+ /// Creates a TryFinally node.
+ public ushort TryFinally(ushort body, ushort @finally)
+ {
+ var bodyType = Nodes.GetSurePresentRef(body).Type;
+ return AddNodeWithChildren(ExpressionType.Try, bodyType, null, extra: 1, new[] { body, @finally });
+ }
+
+ /// Creates a TryCatchFinally node.
+ public ushort TryCatchFinally(ushort body, ushort @finally, params CatchBlock[] handlers)
+ {
+ var bodyType = Nodes.GetSurePresentRef(body).Type;
+ return AddNodeWithChildren(ExpressionType.Try, bodyType, handlers, extra: 2, new[] { body, @finally });
+ }
+
+ /// Creates a TryFault node.
+ public ushort TryFault(ushort body, ushort fault)
+ {
+ var bodyType = Nodes.GetSurePresentRef(body).Type;
+ return AddNodeWithChildren(ExpressionType.Try, bodyType, null, extra: 3, new[] { body, fault });
+ }
+
+ // ── Label / Goto ──────────────────────────────────────────────────────────
+
+ /// Creates a Label node.
+ public ushort Label(LabelTarget target) =>
+ AddNode(ExpressionType.Label, target.Type, target, 0, 0, ExprNode.NoIdx);
+
+ /// Creates a Label node with a default value expression.
+ public ushort Label(LabelTarget target, ushort defaultValue) =>
+ AddNodeWithChildren(ExpressionType.Label, target.Type, target, 0, new[] { defaultValue });
+
+ // Goto Extra: 0=Goto, 1=Return, 2=Break, 3=Continue
+ private ushort MakeGoto(GotoExpressionKind kind, LabelTarget target, ushort valueIdx, Type type)
+ {
+ type ??= typeof(void);
+ if (valueIdx != ExprNode.NoIdx)
+ return AddNodeWithChildren(ExpressionType.Goto, type, target, (byte)kind, new[] { valueIdx });
+ return AddNode(ExpressionType.Goto, type, target, (byte)kind, 0, ExprNode.NoIdx);
+ }
+
+ /// Creates a Goto node.
+ public ushort Goto(LabelTarget target, Type type = null) =>
+ MakeGoto(GotoExpressionKind.Goto, target, ExprNode.NoIdx, type);
+
+ /// Creates a Goto node with a value.
+ public ushort Goto(LabelTarget target, ushort value, Type type = null) =>
+ MakeGoto(GotoExpressionKind.Goto, target, value, type ?? Nodes.GetSurePresentRef(value).Type);
+
+ /// Creates a Return node.
+ public ushort Return(LabelTarget target, Type type = null) =>
+ MakeGoto(GotoExpressionKind.Return, target, ExprNode.NoIdx, type);
+
+ /// Creates a Return node with a value.
+ public ushort Return(LabelTarget target, ushort value, Type type = null) =>
+ MakeGoto(GotoExpressionKind.Return, target, value, type ?? Nodes.GetSurePresentRef(value).Type);
+
+ /// Creates a Break node.
+ public ushort Break(LabelTarget target, Type type = null) =>
+ MakeGoto(GotoExpressionKind.Break, target, ExprNode.NoIdx, type);
+
+ /// Creates a Break node with a value.
+ public ushort Break(LabelTarget target, ushort value, Type type = null) =>
+ MakeGoto(GotoExpressionKind.Break, target, value, type ?? Nodes.GetSurePresentRef(value).Type);
+
+ /// Creates a Continue node.
+ public ushort Continue(LabelTarget target, Type type = null) =>
+ MakeGoto(GotoExpressionKind.Continue, target, ExprNode.NoIdx, type);
+
+ // ── Switch ────────────────────────────────────────────────────────────────
+
+ /// Creates a Switch node.
+ public ushort Switch(ushort switchValue, SwitchCase[] cases, ushort defaultBody = ExprNode.NoIdx,
+ Type type = null, MethodInfo comparison = null)
+ {
+ type ??= cases?.Length > 0 ? cases[0].Body.Type :
+ defaultBody != ExprNode.NoIdx ? Nodes.GetSurePresentRef(defaultBody).Type : typeof(void);
+
+ var children = defaultBody != ExprNode.NoIdx
+ ? new[] { switchValue, defaultBody }
+ : new[] { switchValue };
+
+ return AddNodeWithChildren(ExpressionType.Switch, type,
+ new object[] { cases ?? Array.Empty(), comparison }, 0, children);
+ }
+
+ // ── TypeBinary ────────────────────────────────────────────────────────────
+
+ /// Creates a TypeIs node.
+ public ushort TypeIs(ushort operand, Type type) =>
+ AddNodeWithChildren(ExpressionType.TypeIs, typeof(bool), type, 0, new[] { operand });
+
+ /// Creates a TypeEqual node.
+ public ushort TypeEqual(ushort operand, Type type) =>
+ AddNodeWithChildren(ExpressionType.TypeEqual, typeof(bool), type, 0, new[] { operand });
+
+ // ── Default ───────────────────────────────────────────────────────────────
+
+ /// Creates a Default node.
+ public ushort Default(Type type) =>
+ AddNode(ExpressionType.Default, type, null, 0, 0, ExprNode.NoIdx);
+
+ // ── Invoke ────────────────────────────────────────────────────────────────
+
+ ///
+ /// Creates an Invocation node. The first child is the expression to invoke,
+ /// followed by the arguments.
+ ///
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2080",
+ Justification = "The Invoke method exists on all delegate types")]
+ public ushort Invoke(ushort expression, params ushort[] args)
+ {
+ var exprType = Nodes.GetSurePresentRef(expression).Type;
+ Type returnType;
+ if (exprType.IsSubclassOf(typeof(System.Delegate)) || exprType == typeof(System.Delegate))
+ {
+ var invokeMethod = exprType.GetMethod("Invoke");
+ returnType = invokeMethod?.ReturnType ?? typeof(object);
+ }
+ else
+ returnType = typeof(object);
+
+ var all = new ushort[1 + (args?.Length ?? 0)];
+ all[0] = expression;
+ if (args != null)
+ for (var i = 0; i < args.Length; i++) all[i + 1] = args[i];
+ return AddNodeWithChildren(ExpressionType.Invoke, returnType, null, 0, all);
+ }
+
+ // ── NewArray ──────────────────────────────────────────────────────────────
+
+ /// Creates a NewArrayInit node (array with initializers).
+ public ushort NewArrayInit(Type elementType, params ushort[] elements) =>
+ AddNodeWithChildren(ExpressionType.NewArrayInit, elementType.MakeArrayType(), null, 0, elements);
+
+ /// Creates a NewArrayBounds node (multi-dim array with bounds).
+ public ushort NewArrayBounds(Type elementType, params ushort[] bounds) =>
+ AddNodeWithChildren(ExpressionType.NewArrayBounds, elementType.MakeArrayType(), null, 0, bounds);
+
+ // ── MemberInit ────────────────────────────────────────────────────────────
+
+ ///
+ /// Creates a MemberInit node. The single child is the New expression;
+ /// bindings are stored in .
+ ///
+ public ushort MemberInit(ushort newExpr, params MemberBinding[] bindings)
+ {
+ var type = Nodes.GetSurePresentRef(newExpr).Type;
+ return AddNodeWithChildren(ExpressionType.MemberInit, type, bindings, 0, new[] { newExpr });
+ }
+
+ // ── ListInit ──────────────────────────────────────────────────────────────
+
+ ///
+ /// Creates a ListInit node. The single child is the New expression;
+ /// initializers are stored in .
+ ///
+ public ushort ListInit(ushort newExpr, params ElementInit[] initializers)
+ {
+ var type = Nodes.GetSurePresentRef(newExpr).Type;
+ return AddNodeWithChildren(ExpressionType.ListInit, type, initializers, 0, new[] { newExpr });
+ }
+
+ // ══════════════════════════════════════════════════════════════════════════
+ // Conversion to LightExpression
+ // ══════════════════════════════════════════════════════════════════════════
+
+ ///
+ /// Converts the node at to a .
+ /// The returned expression can be compiled with FastExpressionCompiler.
+ ///
+ [RequiresUnreferencedCode(Trimming.Message)]
+ public LE.Expression ToLightExpression(ushort rootIdx)
+ {
+ var paramCache = new LE.ParameterExpression[Nodes.Count];
+ return ConvertNode(rootIdx, paramCache);
+ }
+
+ ///
+ /// Converts the node at to a .
+ /// Throws if the node is not a Lambda.
+ ///
+ [RequiresUnreferencedCode(Trimming.Message)]
+ public LE.LambdaExpression ToLambdaExpression(ushort rootIdx)
+ {
+ if (Nodes.GetSurePresentRef(rootIdx).NodeType != ExpressionType.Lambda)
+ throw new ArgumentException("The node at rootIdx is not a Lambda node.", nameof(rootIdx));
+ return (LE.LambdaExpression)ToLightExpression(rootIdx);
+ }
+
+ ///
+ /// Converts the node at to a strongly typed
+ /// .
+ ///
+ [RequiresUnreferencedCode(Trimming.Message)]
+ public LE.Expression ToExpression(ushort rootIdx)
+ where TDelegate : System.Delegate =>
+ (LE.Expression)ToLightExpression(rootIdx);
+
+ // ── private conversion helpers ────────────────────────────────────────────
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertNode(ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ ref var node = ref Nodes.GetSurePresentRef(idx);
+ switch (node.NodeType)
+ {
+ case ExpressionType.Constant:
+ return LE.Expression.Constant(node.Obj, node.Type);
+
+ case ExpressionType.Parameter:
+ {
+ var cached = paramCache[idx];
+ if (cached != null) return cached;
+ var param = node.Extra == 1
+ ? LE.Expression.Parameter(node.Type.MakeByRefType(), (string)node.Obj)
+ : LE.Expression.Parameter(node.Type, (string)node.Obj);
+ return paramCache[idx] = param;
+ }
+
+ case ExpressionType.New:
+ return ConvertNew(ref node, idx, paramCache);
+
+ case ExpressionType.Call:
+ return ConvertCall(ref node, idx, paramCache);
+
+ case ExpressionType.MemberAccess:
+ return ConvertMemberAccess(ref node, idx, paramCache);
+
+ case ExpressionType.Index:
+ return ConvertIndex(ref node, idx, paramCache);
+
+ case ExpressionType.Lambda:
+ return ConvertLambda(ref node, idx, paramCache);
+
+ case ExpressionType.Block:
+ return ConvertBlock(ref node, idx, paramCache);
+
+ case ExpressionType.Conditional:
+ return ConvertConditional(ref node, idx, paramCache);
+
+ case ExpressionType.Loop:
+ return ConvertLoop(ref node, idx, paramCache);
+
+ case ExpressionType.Try:
+ return ConvertTry(ref node, idx, paramCache);
+
+ case ExpressionType.Label:
+ return ConvertLabel(ref node, idx, paramCache);
+
+ case ExpressionType.Goto:
+ return ConvertGoto(ref node, idx, paramCache);
+
+ case ExpressionType.Switch:
+ return ConvertSwitch(ref node, idx, paramCache);
+
+ case ExpressionType.TypeIs:
+ return LE.Expression.TypeIs(ConvertChild(idx, 0, paramCache), (Type)node.Obj);
+
+ case ExpressionType.TypeEqual:
+ return LE.Expression.TypeEqual(ConvertChild(idx, 0, paramCache), (Type)node.Obj);
+
+ case ExpressionType.Default:
+ return LE.Expression.Default(node.Type);
+
+ case ExpressionType.Invoke:
+ return ConvertInvoke(ref node, idx, paramCache);
+
+ case ExpressionType.NewArrayInit:
+ return LE.Expression.NewArrayInit(node.Type.GetElementType(), ConvertChildren(idx, paramCache));
+
+ case ExpressionType.NewArrayBounds:
+ return LE.Expression.NewArrayBounds(node.Type.GetElementType(), ConvertChildren(idx, paramCache));
+
+ case ExpressionType.MemberInit:
+ return LE.Expression.MemberInit(
+ (LE.NewExpression)ConvertChild(idx, 0, paramCache),
+ (MemberBinding[])node.Obj);
+
+ case ExpressionType.ListInit:
+ return LE.Expression.ListInit(
+ (LE.NewExpression)ConvertChild(idx, 0, paramCache),
+ (ElementInit[])node.Obj);
+
+ default:
+ return ConvertBinaryOrUnary(ref node, idx, paramCache);
+ }
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertNew(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ if (node.Obj == null)
+ return LE.Expression.New(node.Type);
+ var ctor = (ConstructorInfo)node.Obj;
+ if (node.ChildCount == 0)
+ return LE.Expression.New(ctor);
+ return LE.Expression.New(ctor, ConvertChildren(idx, paramCache));
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertCall(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ var method = (MethodInfo)node.Obj;
+ if (node.Extra == 1) // has instance
+ {
+ var instance = ConvertChild(idx, 0, paramCache);
+ var args = ConvertChildrenFrom(idx, 1, paramCache);
+ return LE.Expression.Call(instance, method, args);
+ }
+ return LE.Expression.Call(method, ConvertChildren(idx, paramCache));
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertMemberAccess(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ if (node.Obj is FieldInfo field)
+ return node.Extra == 1
+ ? LE.Expression.Field(ConvertChild(idx, 0, paramCache), field)
+ : LE.Expression.Field(null, field);
+ var prop = (PropertyInfo)node.Obj;
+ return node.Extra == 1
+ ? LE.Expression.Property(ConvertChild(idx, 0, paramCache), prop)
+ : LE.Expression.Property(null, prop);
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertIndex(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ var obj = ConvertChild(idx, 0, paramCache);
+ var args = ConvertChildrenFrom(idx, 1, paramCache);
+ if (node.Obj is PropertyInfo indexer)
+ return LE.Expression.Property(obj, indexer, args);
+ return LE.Expression.ArrayAccess(obj, args);
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertLambda(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ var paramCount = node.Extra;
+ var parameters = new LE.ParameterExpression[paramCount];
+ for (var i = 0; i < paramCount; i++)
+ {
+ var paramIdx = GetChildIdx(idx, i);
+ // ensure the parameter is in the cache before converting the body
+ if (paramCache[paramIdx] == null)
+ ConvertNode(paramIdx, paramCache);
+ parameters[i] = paramCache[paramIdx];
+ }
+ var body = ConvertChild(idx, paramCount, paramCache);
+ return LE.Expression.Lambda(node.Type, body, parameters);
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertBlock(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ var varCount = node.Extra;
+ var exprCount = node.ChildCount - varCount;
+
+ var variables = new LE.ParameterExpression[varCount];
+ for (var i = 0; i < varCount; i++)
+ {
+ var varIdx = GetChildIdx(idx, i);
+ if (paramCache[varIdx] == null)
+ ConvertNode(varIdx, paramCache);
+ variables[i] = paramCache[varIdx];
+ }
+
+ var exprs = new LE.Expression[exprCount];
+ for (var i = 0; i < exprCount; i++)
+ exprs[i] = ConvertChild(idx, varCount + i, paramCache);
+
+ if (varCount == 0)
+ return LE.Expression.Block(node.Type, exprs);
+ return LE.Expression.Block(node.Type, variables, exprs);
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertConditional(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ var test = ConvertChild(idx, 0, paramCache);
+ var ifTrue = ConvertChild(idx, 1, paramCache);
+ if (node.ChildCount == 2)
+ return LE.Expression.IfThen(test, ifTrue);
+ var ifFalse = ConvertChild(idx, 2, paramCache);
+ return LE.Expression.Condition(test, ifTrue, ifFalse, node.Type);
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertLoop(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ var body = ConvertChild(idx, 0, paramCache);
+ var labels = (LabelTarget[])node.Obj;
+ var breakLabel = labels?[0];
+ var continueLabel = labels?[1];
+ return LE.Expression.Loop(body, breakLabel, continueLabel);
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertTry(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ var body = ConvertChild(idx, 0, paramCache);
+ var handlers = (CatchBlock[])node.Obj;
+ return node.Extra switch
+ {
+ 0 => LE.Expression.TryCatch(body, handlers ?? Array.Empty()),
+ 1 => LE.Expression.TryFinally(body, ConvertChild(idx, 1, paramCache)),
+ 2 => LE.Expression.TryCatchFinally(body, ConvertChild(idx, 1, paramCache),
+ handlers ?? Array.Empty()),
+ 3 => LE.Expression.TryFault(body, ConvertChild(idx, 1, paramCache)),
+ _ => throw new InvalidOperationException($"Unknown Try variant {node.Extra}")
+ };
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertLabel(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ var target = (LabelTarget)node.Obj;
+ if (node.ChildCount == 0)
+ return LE.Expression.Label(target);
+ return LE.Expression.Label(target, ConvertChild(idx, 0, paramCache));
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertGoto(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ var target = (LabelTarget)node.Obj;
+ var kind = (GotoExpressionKind)node.Extra;
+ LE.Expression value = node.ChildCount > 0 ? ConvertChild(idx, 0, paramCache) : null;
+ return LE.Expression.MakeGoto(kind, target, value, node.Type);
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertSwitch(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ var payload = (object[])node.Obj;
+ var cases = (SwitchCase[])payload[0];
+ var comparison = (MethodInfo)payload[1];
+ var switchValue = ConvertChild(idx, 0, paramCache);
+ LE.Expression defaultBody = node.ChildCount > 1 ? ConvertChild(idx, 1, paramCache) : null;
+ return LE.Expression.Switch(node.Type, switchValue, defaultBody, comparison, cases);
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertInvoke(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ var expr = ConvertChild(idx, 0, paramCache);
+ var args = ConvertChildrenFrom(idx, 1, paramCache);
+ return LE.Expression.Invoke(expr, args);
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertBinaryOrUnary(ref ExprNode node, ushort idx, LE.ParameterExpression[] paramCache)
+ {
+ if (node.ChildCount == 2)
+ {
+ var left = ConvertChild(idx, 0, paramCache);
+ var right = ConvertChild(idx, 1, paramCache);
+ var method = node.Obj as MethodInfo;
+ if (node.NodeType == ExpressionType.Coalesce)
+ return LE.Expression.Coalesce(left, right);
+ if (node.NodeType == ExpressionType.ArrayIndex)
+ return LE.Expression.ArrayIndex(left, right);
+ return LE.Expression.MakeBinary(node.NodeType, left, right,
+ liftToNull: false, method, conversion: null);
+ }
+ if (node.ChildCount == 1)
+ {
+ var operand = ConvertChild(idx, 0, paramCache);
+ var method = node.Obj as MethodInfo;
+ return LE.Expression.MakeUnary(node.NodeType, operand, node.Type, method);
+ }
+ throw new NotSupportedException($"Cannot convert ExprNode with NodeType={node.NodeType} and ChildCount={node.ChildCount}");
+ }
+
+ // ── child access helpers ──────────────────────────────────────────────────
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ushort GetChildIdx(ushort parentIdx, int childOffset)
+ {
+ ref var parent = ref Nodes.GetSurePresentRef(parentIdx);
+ return ChildPool.GetSurePresentRef(parent.FirstChildPoolIdx + childOffset);
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression ConvertChild(ushort parentIdx, int childOffset, LE.ParameterExpression[] paramCache) =>
+ ConvertNode(GetChildIdx(parentIdx, childOffset), paramCache);
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression[] ConvertChildren(ushort parentIdx, LE.ParameterExpression[] paramCache)
+ {
+ ref var parent = ref Nodes.GetSurePresentRef(parentIdx);
+ var count = parent.ChildCount;
+ if (count == 0) return Array.Empty();
+ var result = new LE.Expression[count];
+ var poolBase = parent.FirstChildPoolIdx;
+ for (var i = 0; i < count; i++)
+ result[i] = ConvertNode(ChildPool.GetSurePresentRef(poolBase + i), paramCache);
+ return result;
+ }
+
+ [RequiresUnreferencedCode(Trimming.Message)]
+ private LE.Expression[] ConvertChildrenFrom(ushort parentIdx, int startOffset, LE.ParameterExpression[] paramCache)
+ {
+ ref var parent = ref Nodes.GetSurePresentRef(parentIdx);
+ var count = parent.ChildCount - startOffset;
+ if (count <= 0) return Array.Empty();
+ var result = new LE.Expression[count];
+ var poolBase = parent.FirstChildPoolIdx;
+ for (var i = 0; i < count; i++)
+ result[i] = ConvertNode(ChildPool.GetSurePresentRef(poolBase + startOffset + i), paramCache);
+ return result;
+ }
+}
diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/FlatExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/FlatExpressionTests.cs
new file mode 100644
index 00000000..81c06ba3
--- /dev/null
+++ b/test/FastExpressionCompiler.LightExpression.UnitTests/FlatExpressionTests.cs
@@ -0,0 +1,252 @@
+using System;
+using System.Reflection;
+using System.Linq.Expressions;
+using FastExpressionCompiler.LightExpression;
+using LE = FastExpressionCompiler.LightExpression;
+
+namespace FastExpressionCompiler.LightExpression.UnitTests
+{
+ public class FlatExpressionTests : ITest
+ {
+ public int Run()
+ {
+ Constant_and_Lambda_roundtrip();
+ Parameter_reuse_in_body();
+ Binary_arithmetic();
+ Block_with_variable_and_assign();
+ Conditional_ifThen_and_ifThenElse();
+ New_and_Call();
+ Field_and_Property_access();
+ Unary_convert_and_not();
+ Lambda_compiled_and_invoked();
+ Nested_lambda();
+ Loop_with_break();
+ TryCatch_roundtrip();
+ ArrayAccess_and_NewArrayInit();
+ TypeIs_and_TypeEqual();
+ Default_expression();
+ return 16;
+ }
+
+ // ── helpers ───────────────────────────────────────────────────────────
+
+ private static ExprTree E => default;
+
+ // ── tests ─────────────────────────────────────────────────────────────
+
+ public void Constant_and_Lambda_roundtrip()
+ {
+ var e = default(ExprTree);
+ var c = e.Constant(42);
+ var lam = e.Lambda>(c);
+
+ var le = e.ToLightExpression(lam);
+ Asserts.IsNotNull(le);
+ Asserts.AreEqual(ExpressionType.Lambda, le.NodeType);
+
+ var f = ((LE.LambdaExpression)le).CompileFast>();
+ Asserts.AreEqual(42, f());
+ }
+
+ public void Parameter_reuse_in_body()
+ {
+ var e = default(ExprTree);
+ var p = e.Parameter("x");
+ var body = e.Add(p, e.Constant(10));
+ var lam = e.Lambda>(body, p);
+
+ var le = (LE.LambdaExpression)e.ToLightExpression(lam);
+ var f = le.CompileFast>();
+ Asserts.AreEqual(15, f(5));
+ Asserts.AreEqual(20, f(10));
+ }
+
+ public void Binary_arithmetic()
+ {
+ var e = default(ExprTree);
+ var p = e.Parameter("n");
+ var body = e.Multiply(e.Add(p, e.Constant(2)), e.Constant(3));
+ var lam = e.Lambda>(body, p);
+
+ var f = e.ToLambdaExpression(lam).CompileFast>();
+ Asserts.AreEqual(12, f(2)); // (2+2)*3
+ Asserts.AreEqual(15, f(3)); // (3+2)*3
+ }
+
+ public void Block_with_variable_and_assign()
+ {
+ var e = default(ExprTree);
+ var v = e.Variable("tmp");
+ var assign = e.Assign(v, e.Constant(7));
+ var result = e.Add(v, e.Constant(3));
+ var block = e.Block(new[] { v }, new[] { assign, result });
+ var lam = e.Lambda>(block);
+
+ var f = e.ToLambdaExpression(lam).CompileFast>();
+ Asserts.AreEqual(10, f());
+ }
+
+ public void Conditional_ifThen_and_ifThenElse()
+ {
+ var e = default(ExprTree);
+
+ // ifThenElse: x > 0 ? 1 : -1
+ var p = e.Parameter("x");
+ var test = e.GreaterThan(p, e.Constant(0));
+ var body = e.Condition(test, e.Constant(1), e.Constant(-1));
+ var lam = e.Lambda>(body, p);
+ var f = e.ToLambdaExpression(lam).CompileFast>();
+ Asserts.AreEqual(1, f(5));
+ Asserts.AreEqual(-1, f(-1));
+ }
+
+ public class Foo
+ {
+ public int Value;
+ public int X { get; set; }
+ public Foo(int value) => Value = value;
+ public int Add(int n) => Value + n;
+ }
+
+ public void New_and_Call()
+ {
+ var e = default(ExprTree);
+ var ctor = typeof(Foo).GetConstructor(new[] { typeof(int) });
+ var method = typeof(Foo).GetMethod(nameof(Foo.Add));
+
+ var p = e.Parameter("n");
+ var newFoo = e.New(ctor, e.Constant(10));
+ var call = e.Call(newFoo, method, p);
+ var lam = e.Lambda>(call, p);
+
+ var f = e.ToLambdaExpression(lam).CompileFast>();
+ Asserts.AreEqual(15, f(5));
+ }
+
+ public void Field_and_Property_access()
+ {
+ var e = default(ExprTree);
+ var field = typeof(Foo).GetField(nameof(Foo.Value));
+ var prop = typeof(Foo).GetProperty(nameof(Foo.X));
+ var ctor = typeof(Foo).GetConstructor(new[] { typeof(int) });
+
+ // field access: new Foo(42).Value
+ {
+ var newFoo = e.New(ctor, e.Constant(42));
+ var fldAccess = e.Field(newFoo, field);
+ var lam = e.Lambda>(fldAccess);
+ var f = e.ToLambdaExpression(lam).CompileFast>();
+ Asserts.AreEqual(42, f());
+ }
+ }
+
+ public void Unary_convert_and_not()
+ {
+ {
+ var e = default(ExprTree);
+ var p = e.Parameter("x");
+ var conv = e.Convert(p, typeof(long));
+ var lam = e.Lambda>(conv, p);
+ var f = e.ToLambdaExpression(lam).CompileFast>();
+ Asserts.AreEqual(42L, f(42));
+ }
+ {
+ var e = default(ExprTree);
+ var p = e.Parameter("b");
+ var notExpr = e.Not(p);
+ var lam = e.Lambda>(notExpr, p);
+ var f = e.ToLambdaExpression(lam).CompileFast>();
+ Asserts.AreEqual(false, f(true));
+ Asserts.AreEqual(true, f(false));
+ }
+ }
+
+ public void Lambda_compiled_and_invoked()
+ {
+ var e = default(ExprTree);
+ var a = e.Parameter("a");
+ var b = e.Parameter("b");
+ var body = e.Add(a, b);
+ var lam = e.Lambda>(body, a, b);
+
+ var f = e.ToLambdaExpression(lam).CompileFast>();
+ Asserts.AreEqual(7, f(3, 4));
+ }
+
+ public void Nested_lambda()
+ {
+ var e = default(ExprTree);
+ var x = e.Parameter("x");
+ var inner = e.Lambda>(e.Add(x, e.Constant(1)));
+ var outer = e.Lambda>>(inner, x);
+
+ var f = e.ToLambdaExpression(outer).CompileFast>>();
+ Asserts.AreEqual(6, f(5)());
+ }
+
+ public void Loop_with_break()
+ {
+ var e = default(ExprTree);
+ var breakLabel = Expression.Label(typeof(int), "break");
+ var v = e.Variable("i");
+ var assignInit = e.Assign(v, e.Constant(0));
+ var breakExpr = e.Break(breakLabel, e.Convert(v, typeof(int)));
+ var assignInc = e.Assign(v, e.Add(v, e.Constant(1)));
+ var cond = e.IfThen(e.GreaterThanOrEqual(v, e.Constant(3)), breakExpr);
+ var loopBody = e.Block(new[] { assignInc, cond });
+ var loop = e.Loop(loopBody, breakLabel);
+ var block = e.Block(new[] { v }, new[] { assignInit, loop });
+ var lam = e.Lambda>(block);
+
+ var f = e.ToLambdaExpression(lam).CompileFast>();
+ Asserts.AreEqual(3, f());
+ }
+
+ public void TryCatch_roundtrip()
+ {
+ var e = default(ExprTree);
+ var exVar = LE.Expression.Parameter(typeof(InvalidOperationException), "ex");
+ var catchBlock = LE.Expression.Catch(exVar, LE.Expression.Constant(99));
+
+ var body = e.Constant(1);
+ var tryCatch = e.TryCatch(body, catchBlock);
+ var lam = e.Lambda>(tryCatch);
+
+ var f = e.ToLambdaExpression(lam).CompileFast>();
+ Asserts.AreEqual(1, f());
+ }
+
+ public void ArrayAccess_and_NewArrayInit()
+ {
+ var e = default(ExprTree);
+
+ // new int[] { 10, 20, 30 }[1]
+ var arr = e.NewArrayInit(typeof(int), e.Constant(10), e.Constant(20), e.Constant(30));
+ var elem = e.ArrayAccess(arr, e.Constant(1));
+ var lam = e.Lambda>(elem);
+ var f = e.ToLambdaExpression(lam).CompileFast>();
+ Asserts.AreEqual(20, f());
+ }
+
+ public void TypeIs_and_TypeEqual()
+ {
+ var e = default(ExprTree);
+ var p = e.Parameter