diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs index a379899b2..6cd2ae312 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs @@ -60,6 +60,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + // A comparison against a class-constrained type parameter is legal C#, and reference + // comparison is exactly what the tree asks for, so these round-trip as lambdas. + private class ClassConstrainedGeneric where T : class + { + public void ReferenceComparisons() + { + ToCode(X(), (T t) => t == null); + ToCode(X(), (T t) => t != null); + ToCode(X(), (T a, T b) => a == b); + ToCode(X(), (T t) => t != null && t.ToString().Length > 0); + ToCode(X(), (T t) => Check(t == null)); + } + + private static bool Check(bool b) + { + return b; + } + } + private class AssertTest { private struct DataStruct diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs index 96dbe32de..8d0698f08 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs @@ -1092,12 +1092,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms } return new Call(operatorMethod) { Arguments = { leftInst, rightInst } }; } - // A comparison of type parameters has no C# spelling: `v == other` is CS0019 for - // one, and boxing both operands would change the comparison the tree asks for - - // Equal on two T is value equality once T is a value type, box identity is not. - // Leave the tree as the Expression calls that built it. - if (leftType.Kind == TypeKind.TypeParameter || rightType.Kind == TypeKind.TypeParameter) + // A comparison of type parameters has no C# spelling unless the parameter is known + // to be a reference type: `v == other` is CS0019 for an unconstrained one, and boxing + // both operands would change the comparison the tree asks for - Equal on two T is + // value equality once T is a value type, box identity is not. A parameter constrained + // to a reference type compares as one, which is both what the tree asks for and what + // `t == null` spells. Leave the rest as the Expression calls that built it. + if (leftType is { Kind: TypeKind.TypeParameter, IsReferenceType: not true } + || rightType is { Kind: TypeKind.TypeParameter, IsReferenceType: not true }) + { return null; + } var lifting = NullableType.IsNullable(leftType) ? ComparisonLiftingKind.CSharp : ComparisonLiftingKind.None; var utype = NullableType.GetUnderlyingType(leftType); return new Comp(kind, lifting, utype.GetStackType(), utype.GetSign(), leftInst, rightInst);