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
} }
} }
// 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 class AssertTest
{ {
private struct DataStruct private struct DataStruct

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

@ -1092,12 +1092,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
return new Call(operatorMethod) { Arguments = { leftInst, rightInst } }; return new Call(operatorMethod) { Arguments = { leftInst, rightInst } };
} }
// A comparison of type parameters has no C# spelling: `v == other` is CS0019 for // A comparison of type parameters has no C# spelling unless the parameter is known
// one, and boxing both operands would change the comparison the tree asks for - // to be a reference type: `v == other` is CS0019 for an unconstrained one, and boxing
// Equal on two T is value equality once T is a value type, box identity is not. // both operands would change the comparison the tree asks for - Equal on two T is
// Leave the tree as the Expression calls that built it. // value equality once T is a value type, box identity is not. A parameter constrained
if (leftType.Kind == TypeKind.TypeParameter || rightType.Kind == TypeKind.TypeParameter) // 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; return null;
}
var lifting = NullableType.IsNullable(leftType) ? ComparisonLiftingKind.CSharp : ComparisonLiftingKind.None; var lifting = NullableType.IsNullable(leftType) ? ComparisonLiftingKind.CSharp : ComparisonLiftingKind.None;
var utype = NullableType.GetUnderlyingType(leftType); var utype = NullableType.GetUnderlyingType(leftType);
return new Comp(kind, lifting, utype.GetStackType(), utype.GetSign(), leftInst, rightInst); return new Comp(kind, lifting, utype.GetStackType(), utype.GetSign(), leftInst, rightInst);

Loading…
Cancel
Save