diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 150f540bd..eea0ebd0c 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -308,6 +308,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers if (flags.HasFlag(CompilerOptions.UseRoslyn2_10_0) || flags.HasFlag(CompilerOptions.UseRoslynLatest)) { + preprocessorSymbols.Add("ROSLYN2"); preprocessorSymbols.Add("CS70"); preprocessorSymbols.Add("CS71"); preprocessorSymbols.Add("CS72"); @@ -315,6 +316,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers } if (flags.HasFlag(CompilerOptions.UseRoslynLatest)) { + preprocessorSymbols.Add("ROSLYN3"); preprocessorSymbols.Add("CS73"); preprocessorSymbols.Add("CS80"); preprocessorSymbols.Add("VB16"); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs index 56f9c2729..4eacfacde 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs @@ -367,7 +367,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { Console.WriteLine(); } -#if ROSLYN +#if ROSLYN2 // Roslyn 2.9 started invoking op_Equality even if the source code says 'a != b' if (!(a == b)) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs index 45be72188..835c91d67 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs @@ -137,6 +137,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms } return false; } + + bool AnalyzeNegatedCondition(ILInstruction condition) + { + return condition.MatchLogicNot(out var arg) && AnalyzeCondition(arg); + } #endregion #region Main lifting logic @@ -211,11 +216,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms // => comp.lifted[C#](lhs, rhs) return LiftCSharpComparison(comp, comp.Kind); } - else if (trueInst.MatchLdcI4(0) && AnalyzeCondition(falseInst)) + if (trueInst.MatchLdcI4(0) && AnalyzeCondition(falseInst)) { // comp(lhs, rhs) ? false : (v1 != null && ... && vn != null) return LiftCSharpComparison(comp, comp.Kind.Negate()); } + if (falseInst.MatchLdcI4(1) && AnalyzeNegatedCondition(trueInst)) + { + // comp(lhs, rhs) ? !(v1 != null && ... && vn != null) : true + // => !comp.lifted[C#](lhs, rhs) + ILInstruction result = LiftCSharpComparison(comp, comp.Kind); + if (result == null) + return result; + return Comp.LogicNot(result); + } + if (trueInst.MatchLdcI4(1) && AnalyzeNegatedCondition(falseInst)) + { + // comp(lhs, rhs) ? true : !(v1 != null && ... && vn != null) + ILInstruction result = LiftCSharpComparison(comp, comp.Kind.Negate()); + if (result == null) + return result; + return Comp.LogicNot(result); + } } } ILVariable v; @@ -544,7 +566,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return null; } - Call LiftCSharpUserEqualityComparison(CompOrDecimal hasValueComp, ComparisonKind newComparisonKind, ILInstruction nestedIfInst) + ILInstruction LiftCSharpUserEqualityComparison(CompOrDecimal hasValueComp, ComparisonKind newComparisonKind, ILInstruction nestedIfInst) { // User-defined equality operator: // if (comp(call get_HasValue(ldloca nullable1) == call get_HasValue(ldloca nullable2))) @@ -576,11 +598,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms return null; if (!falseInst.MatchLdcI4(newComparisonKind == ComparisonKind.Equality ? 1 : 0)) return null; + bool trueInstNegated = false; + while (trueInst.MatchLogicNot(out var arg)) + { + trueInstNegated = !trueInstNegated; + trueInst = arg; + } if (!(trueInst is Call call)) return null; if (!(call.Method.IsOperator && call.Arguments.Count == 2)) return null; - if (call.Method.Name != (newComparisonKind == ComparisonKind.Equality ? "op_Equality" : "op_Inequality")) + bool expectEqualityOperator = (newComparisonKind == ComparisonKind.Equality) ^ trueInstNegated; + if (call.Method.Name != (expectEqualityOperator ? "op_Equality" : "op_Inequality")) return null; var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method); if (liftedOperator == null) @@ -594,13 +623,66 @@ namespace ICSharpCode.Decompiler.IL.Transforms ) { context.Step("NullableLiftingTransform: C# user-defined (in)equality comparison", nestedIfInst); - return new Call(liftedOperator) { + ILInstruction replacement = new Call(liftedOperator) { Arguments = { left, right }, ConstrainedTo = call.ConstrainedTo, ILStackWasEmpty = call.ILStackWasEmpty, IsTail = call.IsTail, }.WithILRange(call); + if (trueInstNegated) + { + replacement = Comp.LogicNot(replacement); + } + return replacement; + } + return null; + } + + ILInstruction LiftCSharpUserComparison(ILInstruction trueInst, ILInstruction falseInst) + { + // (v1 != null && ... && vn != null) ? trueInst : falseInst + bool trueInstNegated = false; + while (trueInst.MatchLogicNot(out var arg)) + { + trueInstNegated = !trueInstNegated; + trueInst = arg; + } + if (trueInst is Call call && !call.IsLifted + && CSharp.Resolver.CSharpOperators.IsComparisonOperator(call.Method) + && falseInst.MatchLdcI4((call.Method.Name == "op_Inequality") ^ trueInstNegated ? 1 : 0)) + { + // (v1 != null && ... && vn != null) ? call op_LessThan(lhs, rhs) : ldc.i4(0) + var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method); + if ((call.Method.Name == "op_Equality" || call.Method.Name == "op_Inequality") && nullableVars.Count != 1) + { + // Equality is special (returns true if both sides are null), only handle it + // in the normal code path if we're dealing with only a single nullable var + // (comparing nullable with non-nullable). + return null; + } + if (liftedOperator == null) + { + return null; + } + context.Step("Lift user-defined comparison operator", trueInst); + var (left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1], + call.Method.Parameters[0].Type, call.Method.Parameters[1].Type); + if (left != null && right != null && bits.All(0, nullableVars.Count)) + { + ILInstruction result = new Call(liftedOperator) { + Arguments = { left, right }, + ConstrainedTo = call.ConstrainedTo, + ILStackWasEmpty = call.ILStackWasEmpty, + IsTail = call.IsTail + }.WithILRange(call); + if (trueInstNegated) + { + result = Comp.LogicNot(result); + } + return result; + } } + return null; } #endregion @@ -641,35 +723,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms UnderlyingResultType = NullableType.GetUnderlyingType(nullableVars[0].Type).GetStackType() }; } - else if (trueInst is Call call && !call.IsLifted - && CSharp.Resolver.CSharpOperators.IsComparisonOperator(call.Method) - && falseInst.MatchLdcI4(call.Method.Name == "op_Inequality" ? 1 : 0)) - { - // (v1 != null && ... && vn != null) ? call op_LessThan(lhs, rhs) : ldc.i4(0) - var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method); - if ((call.Method.Name == "op_Equality" || call.Method.Name == "op_Inequality") && nullableVars.Count != 1) - { - // Equality is special (returns true if both sides are null), only handle it - // in the normal code path if we're dealing with only a single nullable var - // (comparing nullable with non-nullable). - liftedOperator = null; - } - if (liftedOperator != null) - { - context.Step("Lift user-defined comparison operator", trueInst); - var (left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1], - call.Method.Parameters[0].Type, call.Method.Parameters[1].Type); - if (left != null && right != null && bits.All(0, nullableVars.Count)) - { - return new Call(liftedOperator) { - Arguments = { left, right }, - ConstrainedTo = call.ConstrainedTo, - ILStackWasEmpty = call.ILStackWasEmpty, - IsTail = call.IsTail - }.WithILRange(call); - } - } - } + ILInstruction result = LiftCSharpUserComparison(trueInst, falseInst); + if (result != null) + return result; } ILInstruction lifted; if (nullableVars.Count == 1 && MatchGetValueOrDefault(exprToLift, nullableVars[0]))