@@ -2020,6 +2020,10 @@ public static bool TryEmit(Expression expr, ILGenerator il, ref CompilerContext
20202020 var comparison = (BinaryExpression)expr;
20212021 if (comparison.Method != null && !comparison.IsLifted)
20222022 return TryEmitComparisonMethod(comparison, il, ref context, parent);
2023+ #if LIGHT_EXPRESSION
2024+ if (comparison is LogicalBinaryExpression { IsReferenceComparison: true })
2025+ return TryEmitReferenceComparison(comparison, nodeType == ExpressionType.NotEqual, il, ref context, parent);
2026+ #endif
20232027 if (exprType.IsPrimitive && Interpreter.TryInterpretBool(out var boolResult, expr, context.CompilerFlags))
20242028 {
20252029 if ((parent & ParentFlags.IgnoreResult) == 0)
@@ -2042,9 +2046,9 @@ public static bool TryEmit(Expression expr, ILGenerator il, ref CompilerContext
20422046 var binary = (BinaryExpression)expr;
20432047 var binaryLeft = binary.Left;
20442048 var binaryRight = binary.Right;
2045- if (nodeType == ExpressionType.And && binary.Method == null &&
2049+ if (( nodeType == ExpressionType.And || nodeType == ExpressionType.Or) && binary.Method == null &&
20462050 exprType == typeof(bool?) && binaryLeft.Type == typeof(bool?) && binaryRight.Type == typeof(bool?))
2047- return TryEmitNullableBooleanAnd (binaryLeft, binaryRight, il, ref context, parent);
2051+ return TryEmitNullableBooleanAndOr (binaryLeft, binaryRight, nodeType == ExpressionType.And , il, ref context, parent);
20482052
20492053 return exprType.IsPrimitive
20502054 && TryInterpretAndEmitResult(expr, il, parent, context.CompilerFlags)
@@ -5591,6 +5595,26 @@ private static MethodInfo FindBinaryOperandMethod(
55915595 return null;
55925596 }
55935597
5598+ #if LIGHT_EXPRESSION
5599+ // `ReferenceEqual` and `ReferenceNotEqual` compare the references even when the type defines the `==` operator, e.g. string
5600+ private static bool TryEmitReferenceComparison(BinaryExpression expr, bool isNotEqual, ILGenerator il,
5601+ ref CompilerContext context, ParentFlags parent)
5602+ {
5603+ var flags = parent
5604+ & ~(ParentFlags.IgnoreResult | ParentFlags.InstanceCall |
5605+ ParentFlags.LambdaCall | ParentFlags.ReturnByRef);
5606+
5607+ if (!TryEmit(expr.Left, il, ref context, flags) ||
5608+ !TryEmit(expr.Right, il, ref context, flags))
5609+ return false;
5610+
5611+ il.Demit(OpCodes.Ceq);
5612+ if (isNotEqual)
5613+ EmitEqualToZeroOrNull(il);
5614+ return il.EmitPopIfIgnoreResult(parent);
5615+ }
5616+ #endif
5617+
55945618 private static bool TryEmitComparisonMethod(BinaryExpression expr, ILGenerator il, ref CompilerContext context, ParentFlags parent)
55955619 {
55965620 var method = expr.Method;
@@ -6022,7 +6046,7 @@ private static bool TryEmitArithmetic(Expression left, Expression right, Express
60226046 return true;
60236047 }
60246048
6025- private static bool TryEmitNullableBooleanAnd (Expression left, Expression right, ILGenerator il,
6049+ private static bool TryEmitNullableBooleanAndOr (Expression left, Expression right, bool isAnd , ILGenerator il,
60266050 ref CompilerContext context, ParentFlags parent)
60276051 {
60286052 var flags = (parent
@@ -6038,7 +6062,7 @@ private static bool TryEmitNullableBooleanAnd(Expression left, Expression right,
60386062 return false;
60396063 var rightVar = EmitStoreLocalVariable(il, typeof(bool?));
60406064
6041- var falseLabel = il.DefineLabel();
6065+ var absorbLabel = il.DefineLabel();
60426066 var nullLabel = il.DefineLabel();
60436067 var doneLabel = il.DefineLabel();
60446068
@@ -6052,13 +6076,13 @@ private static bool TryEmitNullableBooleanAnd(Expression left, Expression right,
60526076 il.Demit(OpCodes.Brfalse, nullLabel);
60536077 EmitLoadLocalVariableAddress(il, rightVar);
60546078 il.Demit(OpCodes.Ldfld, typeof(bool?).GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
6055- il.Demit(OpCodes.Brtrue, nullLabel);
6056- il.Demit(OpCodes.Br, falseLabel );
6079+ il.Demit(isAnd ? OpCodes.Brtrue : OpCodes.Brfalse , nullLabel);
6080+ il.Demit(OpCodes.Br, absorbLabel );
60576081
60586082 il.DmarkLabel(leftHasValueLabel);
60596083 EmitLoadLocalVariableAddress(il, leftVar);
60606084 il.Demit(OpCodes.Ldfld, typeof(bool?).GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
6061- il.Demit(OpCodes.Brfalse, falseLabel );
6085+ il.Demit(isAnd ? OpCodes.Brfalse : OpCodes.Brtrue, absorbLabel );
60626086 EmitLoadLocalVariableAddress(il, rightVar);
60636087 EmitMethodCall(il, typeof(bool?).GetNullableHasValueGetterMethod());
60646088 il.Demit(OpCodes.Brfalse, nullLabel);
@@ -6067,8 +6091,8 @@ private static bool TryEmitNullableBooleanAnd(Expression left, Expression right,
60676091 il.Demit(OpCodes.Newobj, typeof(bool?).GetNullableConstructor());
60686092 il.Demit(OpCodes.Br, doneLabel);
60696093
6070- il.DmarkLabel(falseLabel );
6071- il.Demit(OpCodes.Ldc_I4_0);
6094+ il.DmarkLabel(absorbLabel );
6095+ il.Demit(isAnd ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1 );
60726096 il.Demit(OpCodes.Newobj, typeof(bool?).GetNullableConstructor());
60736097 il.Demit(OpCodes.Br, doneLabel);
60746098
@@ -10076,6 +10100,15 @@ internal static StringBuilder CreateExpressionString(this Expression e, StringBu
1007610100
1007710101 if (e is BinaryExpression b)
1007810102 {
10103+ #if LIGHT_EXPRESSION
10104+ if (b is LogicalBinaryExpression { IsReferenceComparison: true })
10105+ {
10106+ sb.Append(e.NodeType == ExpressionType.Equal ? "ReferenceEqual(" : "ReferenceNotEqual(");
10107+ sb.NewLineIndentExpr(b.Left, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(',');
10108+ sb.NewLineIndentExpr(b.Right, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode);
10109+ return sb.Append(')');
10110+ }
10111+ #endif
1007910112 sb.Append("MakeBinary(").Append(typeof(ExpressionType).Name).Append('.').Append(name).Append(',');
1008010113 sb.NewLineIndentExpr(b.Left, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(',');
1008110114 sb.NewLineIndentExpr(b.Right, paramsExprs, uniqueExprs, lts, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode);
@@ -10098,7 +10131,7 @@ internal static StringBuilder CreateExpressionString(this Expression e, StringBu
1009810131 }
1009910132
1010010133 /// <summary>Provides structural equality comparison for expression trees (both LightExpression and System.Linq.Expressions).
10101- /// Use the static <see cref="EqualsTo"/> method as the primary entry point — it creates a temporary struct on the stack with no heap allocation.
10134+ /// Use the static <see cref="EqualsTo"/> method as the primary entry point — it creates a temporary struct on the stack with no heap allocation.
1010210135 /// Parameters are matched by their position within their enclosing lambda or block, and label targets by identity pairing.
1010310136 /// Up to 8 lambda parameters and label targets are stored on the stack; larger numbers spill to a heap array.</summary>
1010410137 public struct ExpressionEqualityComparer : IEqualityComparer<Expression>, IEqualityComparer
@@ -10108,7 +10141,7 @@ public struct ExpressionEqualityComparer : IEqualityComparer<Expression>, IEqual
1010810141 private SmallList<ParameterExpression, Stack8<ParameterExpression>, NoArrayPool<ParameterExpression>> _xps, _yps;
1010910142 private SmallList<LabelTarget, Stack8<LabelTarget>, NoArrayPool<LabelTarget>> _xls, _yls;
1011010143
10111- /// <summary>Structurally compares two expressions. Primary entry point — no heap allocation for the comparer.</summary>
10144+ /// <summary>Structurally compares two expressions. Primary entry point — no heap allocation for the comparer.</summary>
1011210145 [MethodImpl((MethodImplOptions)256)]
1011310146 public static bool EqualsTo(Expression x, Expression y) =>
1011410147 new ExpressionEqualityComparer().Eq(x, y);
@@ -10300,19 +10333,19 @@ private int Hash(Expression expr)
1030010333 }
1030110334 }
1030210335
10303- /// <summary>IEqualityComparer<Expression> implementation — delegates to the static <see cref="EqualsTo"/> for a fresh context per call.</summary>
10336+ /// <summary>IEqualityComparer<Expression> implementation — delegates to the static <see cref="EqualsTo"/> for a fresh context per call.</summary>
1030410337 [MethodImpl((MethodImplOptions)256)]
1030510338 bool IEqualityComparer<Expression>.Equals(Expression x, Expression y) => EqualsTo(x, y);
1030610339
10307- /// <summary>IEqualityComparer<Expression> implementation — delegates to the static <see cref="GetHashCode(Expression)"/>.</summary>
10340+ /// <summary>IEqualityComparer<Expression> implementation — delegates to the static <see cref="GetHashCode(Expression)"/>.</summary>
1030810341 [MethodImpl((MethodImplOptions)256)]
1030910342 int IEqualityComparer<Expression>.GetHashCode(Expression obj) => GetHashCode(obj);
1031010343
10311- /// <summary>Non-generic IEqualityComparer implementation — delegates to the static methods for use with legacy BCL APIs.</summary>
10344+ /// <summary>Non-generic IEqualityComparer implementation — delegates to the static methods for use with legacy BCL APIs.</summary>
1031210345 [MethodImpl((MethodImplOptions)256)]
1031310346 bool IEqualityComparer.Equals(object x, object y) => EqualsTo(x as Expression, y as Expression);
1031410347
10315- /// <summary>Non-generic IEqualityComparer implementation — delegates to the static <see cref="GetHashCode(Expression)"/>.</summary>
10348+ /// <summary>Non-generic IEqualityComparer implementation — delegates to the static <see cref="GetHashCode(Expression)"/>.</summary>
1031610349 [MethodImpl((MethodImplOptions)256)]
1031710350 int IEqualityComparer.GetHashCode(object obj) => GetHashCode(obj as Expression);
1031810351
@@ -11597,6 +11630,19 @@ void PrintPart(Expression part, ref PrintContext ctx)
1159711630 if (containerIgnoresResult | enclosedIn == EnclosedIn.Block)
1159811631 sb.Append("_ = "); // apply for non assignments
1159911632
11633+ #if LIGHT_EXPRESSION
11634+ if (b is LogicalBinaryExpression { IsReferenceComparison: true })
11635+ {
11636+ if (nodeType == ExpressionType.NotEqual)
11637+ sb.Append('!');
11638+ sb.Append("object.ReferenceEquals(");
11639+ b.Left.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx,
11640+ false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(", ");
11641+ return b.Right.ToCSharpExpression(sb, EnclosedIn.AvoidParens, ref ctx,
11642+ false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode).Append(')');
11643+ }
11644+ #endif
11645+
1160011646 sb = sb.AddParenIfNeeded('(', avoidParens);
1160111647 b.Left.ToCSharpExpression(sb, EnclosedIn.ParensByDefault, ref ctx,
1160211648 false, lineIndent, stripNamespace, printType, indentSpaces, notRecognizedToCode);
0 commit comments