diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs index cc503103..6a977c65 100644 --- a/src/FastExpressionCompiler/FastExpressionCompiler.cs +++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs @@ -8562,6 +8562,8 @@ internal static bool TryInterpretBool(ref bool resultBool, Expression expr, Expr // not: negate, arithmetic, convert to bool if (nodeType == ExpressionType.Not) { + if (((UnaryExpression)expr).Method != null) + return false; var operandExpr = ((UnaryExpression)expr).Operand; if (operandExpr.NodeType == ExpressionType.Conditional) operandExpr = Tools.TryReduceConditional((ConditionalExpression)operandExpr); @@ -9583,7 +9585,7 @@ public static T GetFirst(this IEnumerable source) public static Expression TryReduceConditionalTest(Expression testExpr) { // removing Not by turning Equal -> NotEqual, NotEqual -> Equal - if (testExpr.NodeType == ExpressionType.Not) + if (testExpr.NodeType == ExpressionType.Not && ((UnaryExpression)testExpr).Method == null) { // simplify the not `==` -> `!=`, `!=` -> `==` var op = TryReduceConditionalTest(((UnaryExpression)testExpr).Operand); diff --git a/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs b/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs index 0e3921b6..9cf16cc2 100644 --- a/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs +++ b/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs @@ -45,6 +45,7 @@ static void RunLightExpressionTests(object state) 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.MethodBasedNotRegressionTests()); t.Run(new LightExpression.UnitTests.ExtensionReductionIdentityRegressionTests()); t.Run(new LightExpression.UnitTests.ParityTests()); @@ -69,6 +70,7 @@ static void RunLightExpressionTests(object state) fecTests.Run(new ReductionProgressRegressionTests()); fecTests.Run(new ByRefConversionRegressionTests()); fecTests.Run(new RuntimeTypeConstantRegressionTests()); + fecTests.Run(new MethodBasedNotRegressionTests()); fecTests.Run(new CustomReducibleNodeRegressionTests()); fecTests.Run(new ExtensionReductionIdentityRegressionTests()); fecTests.Run(new ParityTests()); diff --git a/test/FastExpressionCompiler.TestsRunner/Program.cs b/test/FastExpressionCompiler.TestsRunner/Program.cs index ef6fc328..f10a72a2 100644 --- a/test/FastExpressionCompiler.TestsRunner/Program.cs +++ b/test/FastExpressionCompiler.TestsRunner/Program.cs @@ -77,6 +77,7 @@ public static void Main(string[] args) lt.Run(new LightExpression.IssueTests.Issue561_NonBooleanEqualityOperator()); lt.Run(new LightExpression.UnitTests.ByRefConversionRegressionTests()); lt.Run(new LightExpression.UnitTests.RuntimeTypeConstantRegressionTests()); + lt.Run(new LightExpression.UnitTests.MethodBasedNotRegressionTests()); lt.Run(new LightExpression.UnitTests.ExtensionReductionIdentityRegressionTests()); lt.Run(new LightExpression.UnitTests.ParityTests()); ltTime = ltStopwatch.ElapsedMilliseconds; @@ -88,6 +89,7 @@ public static void Main(string[] args) st.Run(new ReductionProgressRegressionTests()); st.Run(new ByRefConversionRegressionTests()); st.Run(new RuntimeTypeConstantRegressionTests()); + st.Run(new MethodBasedNotRegressionTests()); st.Run(new CustomReducibleNodeRegressionTests()); st.Run(new ExtensionReductionIdentityRegressionTests()); st.Run(new ParityTests()); diff --git a/test/FastExpressionCompiler.UnitTests/MethodBasedNotRegressionTests.cs b/test/FastExpressionCompiler.UnitTests/MethodBasedNotRegressionTests.cs new file mode 100644 index 00000000..c4462db4 --- /dev/null +++ b/test/FastExpressionCompiler.UnitTests/MethodBasedNotRegressionTests.cs @@ -0,0 +1,87 @@ +using System; +using SysExpr = System.Linq.Expressions.Expression; + +#if LIGHT_EXPRESSION +namespace FastExpressionCompiler.LightExpression.UnitTests; +#else +namespace FastExpressionCompiler.UnitTests; +#endif + +public struct MethodBasedNotRegressionTests : ITestX +{ + public void Run(TestRun t) + { + Explicit_not_method_controls_result(t); + Conditional_preserves_custom_not_semantics(t); + Ignored_result_preserves_custom_not_effects(t); + Nullable_method_receives_null(t); + } + + public void Explicit_not_method_controls_result(TestContext t) + { + var parameter = SysExpr.Parameter(typeof(bool)); + var method = typeof(MethodBasedNotRegressionTests).GetMethod(nameof(Identity)); + foreach (var flags in new[] { CompilerFlags.Default, CompilerFlags.DisableInterpreter }) + foreach (var value in new[] { false, true }) + { + var constant = SysExpr.Lambda>(SysExpr.Not(SysExpr.Constant(value), method)); + var fastConstant = constant.FromSysExpression().CompileFast(true, flags); + if (!t.IsNotNull(fastConstant)) return; + t.AreEqual(value, fastConstant()); + var expression = SysExpr.Lambda>(SysExpr.Not(parameter, method), parameter); + var fast = expression.FromSysExpression().CompileFast(true, flags); + if (!t.IsNotNull(fast)) return; + t.AreEqual(value, fast(value)); + } + } + + public void Conditional_preserves_custom_not_semantics(TestContext t) + { + var parameter = SysExpr.Parameter(typeof(bool)); + var method = typeof(MethodBasedNotRegressionTests).GetMethod(nameof(Identity)); + foreach (var flags in new[] { CompilerFlags.Default, CompilerFlags.DisableInterpreter }) + foreach (var value in new[] { false, true }) + foreach (var shape in new[] { 0, 1, 2 }) + { + System.Linq.Expressions.Expression operand = shape == 0 ? SysExpr.Constant(value) + : shape == 1 ? parameter : SysExpr.Equal(parameter, SysExpr.Constant(true)); + var body = SysExpr.Condition(SysExpr.Not(operand, method), SysExpr.Constant(42), SysExpr.Constant(7)); + var expression = SysExpr.Lambda>(body, parameter); + var fast = expression.FromSysExpression().CompileFast(true, flags); + if (!t.IsNotNull(fast)) return; + Calls = 0; + t.AreEqual(expression.Compile()(value), fast(value)); + t.AreEqual(2, Calls); + } + } + + public void Ignored_result_preserves_custom_not_effects(TestContext t) + { + var method = typeof(MethodBasedNotRegressionTests).GetMethod(nameof(Identity)); + var expression = SysExpr.Lambda>(SysExpr.Block( + SysExpr.Not(SysExpr.Constant(true), method), SysExpr.Constant(7))); + foreach (var flags in new[] { CompilerFlags.Default, CompilerFlags.DisableInterpreter }) + { + var fast = expression.FromSysExpression().CompileFast(true, flags); + if (!t.IsNotNull(fast)) return; + Calls = 0; + t.AreEqual(7, fast()); + t.AreEqual(1, Calls); + } + } + + public void Nullable_method_receives_null(TestContext t) + { + var parameter = SysExpr.Parameter(typeof(bool?)); + var method = typeof(MethodBasedNotRegressionTests).GetMethod(nameof(NullableIdentity)); + var expression = SysExpr.Lambda>(SysExpr.Not(parameter, method), parameter); + var fast = expression.FromSysExpression().CompileFast(true); + if (!t.IsNotNull(fast)) return; + foreach (var value in new bool?[] { null, false, true }) + t.AreEqual(expression.Compile()(value), fast(value)); + } + + public static int Calls; + public static bool Identity(bool value) { ++Calls; return value; } + public static bool? NullableIdentity(bool? value) { ++Calls; return value ?? true; } +}