diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs index 8ee51c68..bddc4f1f 100644 --- a/src/FastExpressionCompiler/FastExpressionCompiler.cs +++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs @@ -3242,7 +3242,8 @@ public static bool TryEmitParameter(ParameterExpression paramExpr, ILGenerator i var paramExprCount = paramExprs.GetParamCount(); var paramType = paramExpr.Type; var isValueType = paramType.IsValueType; - var isParamOrVarByRef = paramExpr.IsByRef && !paramType.IsNullable(); // for the nullable part check the #461 cases with nullable + var isParamOrVarByRef = paramExpr.IsByRef && + (!paramType.IsNullable() || (parent & (ParentFlags.Arithmetic | ParentFlags.AssignmentRightValue)) != 0); // #461 member access uses the nullable address; value operations dereference it var isPassedRef = byRefIndex != -1; // Parameter may represent a variable, so first look if this is the case, @@ -3717,7 +3718,7 @@ private static bool TryEmitConvert(UnaryExpression expr, ILGenerator il, ref Com return TryEmit(opExpr, il, ref context, parent & ~ParentFlags.InstanceAccess); // Emit the operand before going to the fast checks below - if (!TryEmit(opExpr, il, ref context, parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess)) + if (!TryEmit(opExpr, il, ref context, (parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess) | ParentFlags.AssignmentRightValue)) return false; if (sourceType == targetType) diff --git a/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs b/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs index eaeefdf4..acbca4ec 100644 --- a/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs +++ b/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs @@ -43,6 +43,7 @@ static void RunLightExpressionTests(object state) t.Run(new LightExpression.IssueTests.Issue476_System_ExecutionEngineException_with_nullables_on_repeated_calls_to_ConcurrentDictionary()); t.Run(new LightExpression.IssueTests.Issue498_InvalidProgramException_when_using_loop()); t.Run(new LightExpression.IssueTests.Issue499_InvalidProgramException_for_Sorting_and_comparison_function()); + t.Run(new LightExpression.UnitTests.ByRefConversionRegressionTests()); t.Run(new LightExpression.UnitTests.RuntimeTypeConstantRegressionTests()); t.Run(new LightExpression.UnitTests.ParityTests()); @@ -65,6 +66,7 @@ static void RunLightExpressionTests(object state) fecTests.Run(new Issue498_InvalidProgramException_when_using_loop()); fecTests.Run(new Issue499_InvalidProgramException_for_Sorting_and_comparison_function()); fecTests.Run(new ReductionProgressRegressionTests()); + fecTests.Run(new ByRefConversionRegressionTests()); fecTests.Run(new RuntimeTypeConstantRegressionTests()); fecTests.Run(new ParityTests()); diff --git a/test/FastExpressionCompiler.TestsRunner/Program.cs b/test/FastExpressionCompiler.TestsRunner/Program.cs index 7a867a16..5ccbec6a 100644 --- a/test/FastExpressionCompiler.TestsRunner/Program.cs +++ b/test/FastExpressionCompiler.TestsRunner/Program.cs @@ -75,6 +75,7 @@ public static void Main(string[] args) lt.Run(new LightExpression.IssueTests.Issue476_System_ExecutionEngineException_with_nullables_on_repeated_calls_to_ConcurrentDictionary()); lt.Run(new LightExpression.IssueTests.Issue499_InvalidProgramException_for_Sorting_and_comparison_function()); lt.Run(new LightExpression.IssueTests.Issue561_NonBooleanEqualityOperator()); + lt.Run(new LightExpression.UnitTests.ByRefConversionRegressionTests()); lt.Run(new LightExpression.UnitTests.RuntimeTypeConstantRegressionTests()); lt.Run(new LightExpression.UnitTests.ParityTests()); ltTime = ltStopwatch.ElapsedMilliseconds; @@ -84,6 +85,7 @@ public static void Main(string[] args) st.Run(new Issue499_InvalidProgramException_for_Sorting_and_comparison_function()); st.Run(new Issue561_NonBooleanEqualityOperator()); st.Run(new ReductionProgressRegressionTests()); + st.Run(new ByRefConversionRegressionTests()); st.Run(new RuntimeTypeConstantRegressionTests()); st.Run(new ParityTests()); st.Run(new Issue498_InvalidProgramException_when_using_loop()); diff --git a/test/FastExpressionCompiler.UnitTests/ByRefConversionRegressionTests.cs b/test/FastExpressionCompiler.UnitTests/ByRefConversionRegressionTests.cs new file mode 100644 index 00000000..2e6684cf --- /dev/null +++ b/test/FastExpressionCompiler.UnitTests/ByRefConversionRegressionTests.cs @@ -0,0 +1,54 @@ +using System; +using System.Reflection; +using SysExpr = System.Linq.Expressions.Expression; + +#if LIGHT_EXPRESSION +using Expr = FastExpressionCompiler.LightExpression.Expression; +namespace FastExpressionCompiler.LightExpression.UnitTests; +#else +using Expr = System.Linq.Expressions.Expression; +namespace FastExpressionCompiler.UnitTests; +#endif + +public struct ByRefConversionRegressionTests : ITestX +{ + public void Run(TestRun t) + { + By_ref_values_box_inside_array_initializers(t); + By_ref_nullable_values_box_inside_array_initializers(t); + } + + public void By_ref_values_box_inside_array_initializers(TestContext t) => Check(t, false); + public void By_ref_nullable_values_box_inside_array_initializers(TestContext t) => Check(t, true); + + private static void Check(TestContext t, bool nullable) + { + Type[] types = { typeof(short), typeof(int), typeof(long), typeof(float), typeof(double), typeof(decimal), typeof(Tagged) }; + object[] values = { (short)42, 42, 42L, 42f, 42d, 42m, new Tagged { Text = "value" } }; + for (var i = 0; i < types.Length; ++i) + { + var type = nullable ? typeof(Nullable<>).MakeGenericType(types[i]) : types[i]; + var delegateType = typeof(Read<>).MakeGenericType(type); + var parameter = SysExpr.Parameter(type.MakeByRefType()); + var system = SysExpr.Lambda(delegateType, SysExpr.NewArrayInit(typeof(object), + SysExpr.Convert(parameter, typeof(object)), SysExpr.Convert(parameter, typeof(object))), parameter).Compile(); + var fastParameter = Expr.Parameter(type.MakeByRefType()); + var fast = Expr.Lambda(delegateType, Expr.NewArrayInit(typeof(object), + Expr.Convert(fastParameter, typeof(object)), Expr.Convert(fastParameter, typeof(object))), fastParameter).CompileFast(true); + if (!t.IsNotNull(fast)) return; + Compare(t, system, fast, values[i]); + if (nullable) Compare(t, system, fast, null); + } + } + + private static void Compare(TestContext t, Delegate system, Delegate fast, object value) + { + var expected = (object[])system.DynamicInvoke(new[] { value }); + var actual = (object[])fast.DynamicInvoke(new[] { value }); + t.AreEqual(expected[0], actual[0]); + t.AreEqual(expected[1], actual[1]); + } + + public delegate object[] Read(ref T value); + public struct Tagged { public string Text; } +}