@@ -3000,17 +3000,17 @@ private static bool TryEmitSimpleUnaryExpression(UnaryExpression expr, Expressio
30003000 var endLabel = il.DefineLabel();
30013001
30023002 EmitLoadLocalVariableAddress(il, valueVar);
3003- il.Demit(OpCodes.Call, typeof( bool?).GetNullableHasValueGetterMethod() );
3003+ il.Demit(OpCodes.Call, Tools.NullableReflected< bool>.HasValueGetterMethod );
30043004 il.Demit(OpCodes.Brfalse, nullLabel);
30053005
30063006 EmitLoadLocalVariableAddress(il, valueVar);
3007- il.Demit(OpCodes.Ldfld, typeof( bool?).GetNullableValueUnsafeAkaGetValueOrDefaultMethod() );
3007+ il.Demit(OpCodes.Ldfld, Tools.NullableReflected< bool>.ValueField );
30083008 if (nodeType == ExpressionType.IsFalse)
30093009 {
30103010 il.Demit(OpCodes.Ldc_I4_0);
30113011 il.Demit(OpCodes.Ceq);
30123012 }
3013- il.Demit(OpCodes.Newobj, typeof( bool?).GetNullableConstructor() );
3013+ il.Demit(OpCodes.Newobj, Tools.NullableReflected< bool>.Constructor );
30143014 il.Demit(OpCodes.Br, endLabel);
30153015
30163016 il.DmarkLabel(nullLabel);
@@ -6062,42 +6062,31 @@ private static bool TryEmitNullableBooleanAndOr(Expression left, Expression righ
60626062 return false;
60636063 var rightVar = EmitStoreLocalVariable(il, typeof(bool?));
60646064
6065- var absorbLabel = il.DefineLabel();
6066- var nullLabel = il.DefineLabel();
6065+ var hasValueGetter = Tools.NullableReflected<bool>.HasValueGetterMethod;
6066+ var valueField = Tools.NullableReflected<bool>.ValueField;
6067+
6068+ // The same shape as System emits: no `newobj` and no extra locals, the result is one of the operands.
6069+ // And: left.value ? right : (left.has | right.value) ? left : right
6070+ // Or: left.value ? left : (right.value | left.has) ? right : left
6071+ var sameLabel = il.DefineLabel();
60676072 var doneLabel = il.DefineLabel();
60686073
60696074 EmitLoadLocalVariableAddress(il, leftVar);
6070- EmitMethodCall(il, typeof(bool?).GetNullableHasValueGetterMethod());
6071- var leftHasValueLabel = il.DefineLabel();
6072- il.Demit(OpCodes.Brtrue, leftHasValueLabel);
6073-
6074- EmitLoadLocalVariableAddress(il, rightVar);
6075- EmitMethodCall(il, typeof(bool?).GetNullableHasValueGetterMethod());
6076- il.Demit(OpCodes.Brfalse, nullLabel);
6077- EmitLoadLocalVariableAddress(il, rightVar);
6078- il.Demit(OpCodes.Ldfld, typeof(bool?).GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
6079- il.Demit(isAnd ? OpCodes.Brtrue : OpCodes.Brfalse, nullLabel);
6080- il.Demit(OpCodes.Br, absorbLabel);
6075+ il.Demit(OpCodes.Ldfld, valueField);
6076+ il.Demit(OpCodes.Brtrue, sameLabel);
60816077
6082- il.DmarkLabel(leftHasValueLabel);
60836078 EmitLoadLocalVariableAddress(il, leftVar);
6084- il.Demit(OpCodes.Ldfld, typeof(bool?).GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
6085- il.Demit(isAnd ? OpCodes.Brfalse : OpCodes.Brtrue, absorbLabel);
6086- EmitLoadLocalVariableAddress(il, rightVar);
6087- EmitMethodCall(il, typeof(bool?).GetNullableHasValueGetterMethod());
6088- il.Demit(OpCodes.Brfalse, nullLabel);
6079+ EmitMethodCall(il, hasValueGetter);
60896080 EmitLoadLocalVariableAddress(il, rightVar);
6090- il.Demit(OpCodes.Ldfld, typeof(bool?).GetNullableValueUnsafeAkaGetValueOrDefaultMethod() );
6091- il.Demit(OpCodes.Newobj, typeof(bool?).GetNullableConstructor() );
6092- il.Demit(OpCodes.Br, doneLabel );
6081+ il.Demit(OpCodes.Ldfld, valueField );
6082+ il.Demit(OpCodes.Or );
6083+ il.Demit(OpCodes.Brfalse, sameLabel );
60936084
6094- il.DmarkLabel(absorbLabel);
6095- il.Demit(isAnd ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1);
6096- il.Demit(OpCodes.Newobj, typeof(bool?).GetNullableConstructor());
6085+ EmitLoadLocalVariable(il, isAnd ? leftVar : rightVar);
60976086 il.Demit(OpCodes.Br, doneLabel);
60986087
6099- il.DmarkLabel(nullLabel );
6100- EmitLoadLocalVariable(il, InitValueTypeVariable(il, typeof(bool?)) );
6088+ il.DmarkLabel(sameLabel );
6089+ EmitLoadLocalVariable(il, isAnd ? rightVar : leftVar );
61016090
61026091 il.DmarkLabel(doneLabel);
61036092 return il.EmitPopIfIgnoreResult(parent);
@@ -6116,21 +6105,24 @@ private static bool TryEmitNullableBooleanNot(Expression operand, ILGenerator il
61166105 return false;
61176106 var opVar = EmitStoreLocalVariable(il, typeof(bool?));
61186107
6108+ var hasValueGetter = Tools.NullableReflected<bool>.HasValueGetterMethod;
6109+ var valueField = Tools.NullableReflected<bool>.ValueField;
61196110 var nullLabel = il.DefineLabel();
61206111 var doneLabel = il.DefineLabel();
61216112
61226113 EmitLoadLocalVariableAddress(il, opVar);
6123- EmitMethodCall(il, typeof(bool?).GetNullableHasValueGetterMethod() );
6114+ EmitMethodCall(il, hasValueGetter );
61246115 il.Demit(OpCodes.Brfalse, nullLabel);
61256116 EmitLoadLocalVariableAddress(il, opVar);
6126- il.Demit(OpCodes.Ldfld, typeof(bool?).GetNullableValueUnsafeAkaGetValueOrDefaultMethod() );
6117+ il.Demit(OpCodes.Ldfld, valueField );
61276118 il.Demit(OpCodes.Ldc_I4_0);
61286119 il.Demit(OpCodes.Ceq);
6129- il.Demit(OpCodes.Newobj, typeof( bool?).GetNullableConstructor() );
6120+ il.Demit(OpCodes.Newobj, Tools.NullableReflected< bool>.Constructor );
61306121 il.Demit(OpCodes.Br, doneLabel);
61316122
6123+ // null stays null, so the operand itself is the result
61326124 il.DmarkLabel(nullLabel);
6133- EmitLoadLocalVariable(il, InitValueTypeVariable(il, typeof(bool?)) );
6125+ EmitLoadLocalVariable(il, opVar );
61346126
61356127 il.DmarkLabel(doneLabel);
61366128 return il.EmitPopIfIgnoreResult(parent);
@@ -6149,49 +6141,59 @@ private static bool TryEmitNullableBooleanShortCircuit(Expression left, Expressi
61496141 return false;
61506142 var leftVar = EmitStoreLocalVariable(il, typeof(bool?));
61516143
6152- var hasValueGetter = typeof(bool?).GetNullableHasValueGetterMethod();
6153- var valueField = typeof(bool?).GetNullableValueUnsafeAkaGetValueOrDefaultMethod();
6154- var shortLabel = il.DefineLabel();
6155- var leftNullLabel = il.DefineLabel();
6156- var nullResultLabel = il.DefineLabel();
6144+ var hasValueGetter = Tools.NullableReflected<bool>.HasValueGetterMethod;
6145+ var valueField = Tools.NullableReflected<bool>.ValueField;
6146+ var evalRightLabel = il.DefineLabel();
6147+ var leftResultLabel = il.DefineLabel();
61576148 var doneLabel = il.DefineLabel();
6158- var decidingOp = isAnd ? OpCodes.Brfalse : OpCodes.Brtrue;
61596149
6160- EmitLoadLocalVariableAddress(il, leftVar);
6161- EmitMethodCall(il, hasValueGetter);
6162- il.Demit(OpCodes.Brfalse, leftNullLabel);
6150+ // The same shape as System emits: the result is one of the operands, no `newobj` and no extra locals
61636151 EmitLoadLocalVariableAddress(il, leftVar);
61646152 il.Demit(OpCodes.Ldfld, valueField);
6165- il.Demit(decidingOp, shortLabel);
6153+ if (isAnd)
6154+ {
6155+ // left == true: evaluate right, left == false: the result is left, left == null: evaluate right
6156+ il.Demit(OpCodes.Brtrue, evalRightLabel);
6157+ EmitLoadLocalVariableAddress(il, leftVar);
6158+ EmitMethodCall(il, hasValueGetter);
6159+ il.Demit(OpCodes.Brtrue, leftResultLabel);
6160+ }
6161+ else
6162+ il.Demit(OpCodes.Brtrue, leftResultLabel);
61666163
6167- il.DmarkLabel(leftNullLabel );
6164+ il.DmarkLabel(evalRightLabel );
61686165 if (!TryEmit(right, il, ref context, flags))
61696166 return false;
61706167 var rightVar = EmitStoreLocalVariable(il, typeof(bool?));
61716168
6172- // left is either null or the non-deciding value, so the result is the right one
6173- EmitLoadLocalVariableAddress(il, leftVar);
6174- EmitMethodCall(il, hasValueGetter);
6175- var leftNullBranch = il.DefineLabel();
6176- il.Demit(OpCodes.Brfalse, leftNullBranch);
6177- EmitLoadLocalVariable(il, rightVar);
6178- il.Demit(OpCodes.Br, doneLabel);
6179-
6180- il.DmarkLabel(leftNullBranch);
6181- EmitLoadLocalVariableAddress(il, rightVar);
6182- EmitMethodCall(il, hasValueGetter);
6183- il.Demit(OpCodes.Brfalse, nullResultLabel);
6184- EmitLoadLocalVariableAddress(il, rightVar);
6185- il.Demit(OpCodes.Ldfld, valueField);
6186- il.Demit(decidingOp, shortLabel);
6187-
6188- il.DmarkLabel(nullResultLabel);
6189- EmitLoadLocalVariable(il, InitValueTypeVariable(il, typeof(bool?)));
6169+ if (isAnd)
6170+ {
6171+ // left.value ? right : right.value ? left : right
6172+ var rightResultLabel = il.DefineLabel();
6173+ EmitLoadLocalVariableAddress(il, leftVar);
6174+ il.Demit(OpCodes.Ldfld, valueField);
6175+ il.Demit(OpCodes.Brtrue, rightResultLabel);
6176+ EmitLoadLocalVariableAddress(il, rightVar);
6177+ il.Demit(OpCodes.Ldfld, valueField);
6178+ il.Demit(OpCodes.Brtrue, leftResultLabel);
6179+ il.DmarkLabel(rightResultLabel);
6180+ EmitLoadLocalVariable(il, rightVar);
6181+ }
6182+ else
6183+ {
6184+ // (right.value | left.has) ? right : left
6185+ EmitLoadLocalVariableAddress(il, rightVar);
6186+ il.Demit(OpCodes.Ldfld, valueField);
6187+ EmitLoadLocalVariableAddress(il, leftVar);
6188+ EmitMethodCall(il, hasValueGetter);
6189+ il.Demit(OpCodes.Or);
6190+ il.Demit(OpCodes.Brfalse, leftResultLabel);
6191+ EmitLoadLocalVariable(il, rightVar);
6192+ }
61906193 il.Demit(OpCodes.Br, doneLabel);
61916194
6192- il.DmarkLabel(shortLabel);
6193- il.Demit(isAnd ? OpCodes.Ldc_I4_0 : OpCodes.Ldc_I4_1);
6194- il.Demit(OpCodes.Newobj, typeof(bool?).GetNullableConstructor());
6195+ il.DmarkLabel(leftResultLabel);
6196+ EmitLoadLocalVariable(il, leftVar);
61956197
61966198 il.DmarkLabel(doneLabel);
61976199 return il.EmitPopIfIgnoreResult(parent);
@@ -10131,7 +10133,7 @@ internal static StringBuilder CreateExpressionString(this Expression e, StringBu
1013110133 }
1013210134
1013310135 /// <summary>Provides structural equality comparison for expression trees (both LightExpression and System.Linq.Expressions).
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.
10136+ /// Use the static <see cref="EqualsTo"/> method as the primary entry point - it creates a temporary struct on the stack with no heap allocation.
1013510137 /// Parameters are matched by their position within their enclosing lambda or block, and label targets by identity pairing.
1013610138 /// Up to 8 lambda parameters and label targets are stored on the stack; larger numbers spill to a heap array.</summary>
1013710139 public struct ExpressionEqualityComparer : IEqualityComparer<Expression>, IEqualityComparer
@@ -10141,7 +10143,7 @@ public struct ExpressionEqualityComparer : IEqualityComparer<Expression>, IEqual
1014110143 private SmallList<ParameterExpression, Stack8<ParameterExpression>, NoArrayPool<ParameterExpression>> _xps, _yps;
1014210144 private SmallList<LabelTarget, Stack8<LabelTarget>, NoArrayPool<LabelTarget>> _xls, _yls;
1014310145
10144- /// <summary>Structurally compares two expressions. Primary entry point — no heap allocation for the comparer.</summary>
10146+ /// <summary>Structurally compares two expressions. Primary entry point - no heap allocation for the comparer.</summary>
1014510147 [MethodImpl((MethodImplOptions)256)]
1014610148 public static bool EqualsTo(Expression x, Expression y) =>
1014710149 new ExpressionEqualityComparer().Eq(x, y);
@@ -10333,19 +10335,19 @@ private int Hash(Expression expr)
1033310335 }
1033410336 }
1033510337
10336- /// <summary>IEqualityComparer<Expression> implementation — delegates to the static <see cref="EqualsTo"/> for a fresh context per call.</summary>
10338+ /// <summary>IEqualityComparer<Expression> implementation - delegates to the static <see cref="EqualsTo"/> for a fresh context per call.</summary>
1033710339 [MethodImpl((MethodImplOptions)256)]
1033810340 bool IEqualityComparer<Expression>.Equals(Expression x, Expression y) => EqualsTo(x, y);
1033910341
10340- /// <summary>IEqualityComparer<Expression> implementation — delegates to the static <see cref="GetHashCode(Expression)"/>.</summary>
10342+ /// <summary>IEqualityComparer<Expression> implementation - delegates to the static <see cref="GetHashCode(Expression)"/>.</summary>
1034110343 [MethodImpl((MethodImplOptions)256)]
1034210344 int IEqualityComparer<Expression>.GetHashCode(Expression obj) => GetHashCode(obj);
1034310345
10344- /// <summary>Non-generic IEqualityComparer implementation — delegates to the static methods for use with legacy BCL APIs.</summary>
10346+ /// <summary>Non-generic IEqualityComparer implementation - delegates to the static methods for use with legacy BCL APIs.</summary>
1034510347 [MethodImpl((MethodImplOptions)256)]
1034610348 bool IEqualityComparer.Equals(object x, object y) => EqualsTo(x as Expression, y as Expression);
1034710349
10348- /// <summary>Non-generic IEqualityComparer implementation — delegates to the static <see cref="GetHashCode(Expression)"/>.</summary>
10350+ /// <summary>Non-generic IEqualityComparer implementation - delegates to the static <see cref="GetHashCode(Expression)"/>.</summary>
1034910351 [MethodImpl((MethodImplOptions)256)]
1035010352 int IEqualityComparer.GetHashCode(object obj) => GetHashCode(obj as Expression);
1035110353
0 commit comments