Skip to content

Commit fc01519

Browse files
committed
more from #558; RefEquals,Or(bool?)
1 parent 1cca592 commit fc01519

3 files changed

Lines changed: 299 additions & 27 deletions

File tree

‎src/FastExpressionCompiler.LightExpression/Expression.cs‎

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -628,13 +628,13 @@ public static UnaryExpression Increment(Expression expression) =>
628628
/// <summary>Returns whether the expression evaluates to false.</summary>
629629
public static UnaryExpression IsFalse(Expression expression) =>
630630
Nullable.GetUnderlyingType(expression.Type) != null
631-
? new TypedUnaryExpression<bool?>(ExpressionType.IsFalse, expression)
631+
? new TypedUnaryExpression<bool?>(ExpressionType.IsFalse, expression)
632632
: new TypedUnaryExpression<bool>(ExpressionType.IsFalse, expression);
633633

634634
/// <summary>Returns whether the expression evaluates to true.</summary>
635635
public static UnaryExpression IsTrue(Expression expression) =>
636-
Nullable.GetUnderlyingType(expression.Type) != null
637-
? new TypedUnaryExpression<bool?>(ExpressionType.IsTrue, expression)
636+
Nullable.GetUnderlyingType(expression.Type) != null
637+
? new TypedUnaryExpression<bool?>(ExpressionType.IsTrue, expression)
638638
: new TypedUnaryExpression<bool>(ExpressionType.IsTrue, expression);
639639

640640
/// <summary>Creates a UnaryExpression, given an operand and its type</summary>
@@ -2152,11 +2152,11 @@ public static SwitchCase SwitchCase(Expression body, params Expression[] testVal
21522152

21532153
/// <summary>Creates a BinaryExpression that represents a reference equality comparison.</summary>
21542154
public static BinaryExpression ReferenceEqual(Expression left, Expression right) =>
2155-
new LogicalBinaryExpression(ExpressionType.Equal, left, right);
2155+
new LogicalBinaryExpression(ExpressionType.Equal, left, right, isReferenceComparison: true);
21562156

21572157
/// <summary>Creates a BinaryExpression that represents a reference inequality comparison.</summary>
21582158
public static BinaryExpression ReferenceNotEqual(Expression left, Expression right) =>
2159-
new LogicalBinaryExpression(ExpressionType.NotEqual, left, right);
2159+
new LogicalBinaryExpression(ExpressionType.NotEqual, left, right, isReferenceComparison: true);
21602160

21612161
/// <summary>Creates a BinaryExpression that represents a coalescing operation.</summary>
21622162
[RequiresUnreferencedCode(Trimming.Message)]
@@ -2927,6 +2927,14 @@ internal static Expression ConvertToLightExpression(SysExpr sysExpr, ref SmallLi
29272927
var left = be.Left.ToLightExpression(ref exprsConverted);
29282928
var raft = be.Right.ToLightExpression(ref exprsConverted);
29292929
var conv = be.Conversion?.ToLightExpression(ref exprsConverted) as LambdaExpression;
2930+
2931+
// System `ReferenceEqual`/`ReferenceNotEqual` are `Equal`/`NotEqual` without the method for the reference type operands,
2932+
// for the types with `==` operator System binds the method, so the missing method means the reference comparison
2933+
if ((nodeType == ExpressionType.Equal | nodeType == ExpressionType.NotEqual) &&
2934+
be.Method == null && !be.IsLiftedToNull && be.Conversion == null &&
2935+
!be.Left.Type.IsValueType && !be.Right.Type.IsValueType)
2936+
return nodeType == ExpressionType.Equal ? Expression.ReferenceEqual(left, raft) : Expression.ReferenceNotEqual(left, raft);
2937+
29302938
return Expression.MakeBinary(nodeType, left, raft, be.IsLiftedToNull, be.Method, conv);
29312939
}
29322940

@@ -3563,11 +3571,23 @@ internal sealed class LogicalBinaryExpression : BinaryExpression
35633571
{
35643572
public override ExpressionType NodeType { get; }
35653573
public override Type Type => typeof(bool);
3566-
internal LogicalBinaryExpression(ExpressionType nodeType, Expression left, Expression right) : base(left, right) =>
3574+
internal readonly bool IsReferenceComparison;
3575+
internal LogicalBinaryExpression(ExpressionType nodeType, Expression left, Expression right, bool isReferenceComparison = false) : base(left, right)
3576+
{
35673577
NodeType = nodeType;
3568-
3578+
IsReferenceComparison = isReferenceComparison;
3579+
}
35693580
internal override SysExpr CreateSysExpression(ref SmallList<LightAndSysExpr> exprsConverted)
35703581
{
3582+
if (IsReferenceComparison)
3583+
{
3584+
var refLeft = Left.ToExpression(ref exprsConverted);
3585+
var refRight = Right.ToExpression(ref exprsConverted);
3586+
return NodeType == ExpressionType.Equal
3587+
? SysExpr.ReferenceEqual(refLeft, refRight)
3588+
: SysExpr.ReferenceNotEqual(refLeft, refRight);
3589+
}
3590+
35713591
if (NodeType == ExpressionType.Equal ||
35723592
NodeType == ExpressionType.NotEqual)
35733593
{
@@ -4077,7 +4097,7 @@ public class TwoArgumentsNewExpression : NewExpression
40774097
public override IReadOnlyList<Expression> Arguments => new[] { Argument0, Argument1 };
40784098
public override int ArgumentCount => 2;
40794099
public override Expression GetArgument(int i) => _args.GetSurePresentRef(i).AsExpr();
4080-
internal TwoArgumentsNewExpression(ConstructorInfo constructor, object a0, object a1) : base(constructor) =>
4100+
internal TwoArgumentsNewExpression(ConstructorInfo constructor, object a0, object a1) : base(constructor) =>
40814101
_args = new(a0, a1);
40824102
}
40834103

@@ -4230,7 +4250,7 @@ public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaI
42304250
if ((r = ExpressionCompiler.TryCollectInfo(ref context, Argument1, nestedLambda, ref rootNestedLambdas)) != Result.OK) return r;
42314251
if ((r = ExpressionCompiler.TryCollectInfo(ref context, Argument2, nestedLambda, ref rootNestedLambdas)) != Result.OK) return r;
42324252
if ((r = ExpressionCompiler.TryCollectInfo(ref context, Argument3, nestedLambda, ref rootNestedLambdas)) != Result.OK) return r;
4233-
return ExpressionCompiler.TryCollectInfo(ref context, Argument4, nestedLambda, ref rootNestedLambdas);
4253+
return ExpressionCompiler.TryCollectInfo(ref context, Argument4, nestedLambda, ref rootNestedLambdas);
42344254
}
42354255

42364256
[RequiresUnreferencedCode(Trimming.Message)]
@@ -4284,7 +4304,7 @@ public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaI
42844304
if ((r = ExpressionCompiler.TryCollectInfo(ref context, Argument2, nestedLambda, ref rootNestedLambdas)) != Result.OK) return r;
42854305
if ((r = ExpressionCompiler.TryCollectInfo(ref context, Argument3, nestedLambda, ref rootNestedLambdas)) != Result.OK) return r;
42864306
if ((r = ExpressionCompiler.TryCollectInfo(ref context, Argument4, nestedLambda, ref rootNestedLambdas)) != Result.OK) return r;
4287-
return ExpressionCompiler.TryCollectInfo(ref context, Argument5, nestedLambda, ref rootNestedLambdas);
4307+
return ExpressionCompiler.TryCollectInfo(ref context, Argument5, nestedLambda, ref rootNestedLambdas);
42884308
}
42894309

42904310
[RequiresUnreferencedCode(Trimming.Message)]
@@ -4341,7 +4361,7 @@ public override Result TryCollectInfo(ref CompilerContext context, NestedLambdaI
43414361
if ((r = ExpressionCompiler.TryCollectInfo(ref context, Argument3, nestedLambda, ref rootNestedLambdas)) != 0) return r;
43424362
if ((r = ExpressionCompiler.TryCollectInfo(ref context, Argument4, nestedLambda, ref rootNestedLambdas)) != 0) return r;
43434363
if ((r = ExpressionCompiler.TryCollectInfo(ref context, Argument5, nestedLambda, ref rootNestedLambdas)) != 0) return r;
4344-
return ExpressionCompiler.TryCollectInfo(ref context, Argument6, nestedLambda, ref rootNestedLambdas);
4364+
return ExpressionCompiler.TryCollectInfo(ref context, Argument6, nestedLambda, ref rootNestedLambdas);
43454365
}
43464366

43474367
[RequiresUnreferencedCode(Trimming.Message)]

‎src/FastExpressionCompiler/FastExpressionCompiler.cs‎

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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&lt;Expression&gt; implementation — delegates to the static <see cref="EqualsTo"/> for a fresh context per call.</summary>
10336+
/// <summary>IEqualityComparer&lt;Expression&gt; 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&lt;Expression&gt; implementation — delegates to the static <see cref="GetHashCode(Expression)"/>.</summary>
10340+
/// <summary>IEqualityComparer&lt;Expression&gt; 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

Comments
 (0)