Browse Source

TransformExpressionTrees: compare reference-constrained type parameters

Declining every type-parameter operand was too broad. The reason a type
parameter has no lambda spelling is that `v == other` is CS0019 and boxing
both operands compares box identity where the tree compares values - and both
only apply while the parameter may be a value type. A parameter constrained to
a reference type compares as a reference, which is what the tree asks for and
what `t == null` spells, so it converts like any other reference comparison.

IsReferenceType is the distinction the type system already makes here:
TypeUtils.GetStackType maps a type parameter to Obj or VT by the same
question. An unconstrained parameter answers null and keeps declining.

Assisted-by: Claude:claude-opus-5:Claude Code
pull/4091/head
Siegfried Pammer 6 days ago
parent
commit
d797d4abb4
  1. 19
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs
  2. 15
      ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs

19
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs

@ -60,6 +60,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -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<T> 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

15
ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs

@ -1092,12 +1092,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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);

Loading…
Cancel
Save