Skip to content

Commit d797d4a

Browse files
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
1 parent 03410c4 commit d797d4a

2 files changed

Lines changed: 29 additions & 5 deletions

File tree

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,25 @@ public GenericClassWithMultipleCtors(int x)
6060
}
6161
}
6262

63+
// A comparison against a class-constrained type parameter is legal C#, and reference
64+
// comparison is exactly what the tree asks for, so these round-trip as lambdas.
65+
private class ClassConstrainedGeneric<T> where T : class
66+
{
67+
public void ReferenceComparisons()
68+
{
69+
ToCode(X(), (T t) => t == null);
70+
ToCode(X(), (T t) => t != null);
71+
ToCode(X(), (T a, T b) => a == b);
72+
ToCode(X(), (T t) => t != null && t.ToString().Length > 0);
73+
ToCode(X(), (T t) => Check(t == null));
74+
}
75+
76+
private static bool Check(bool b)
77+
{
78+
return b;
79+
}
80+
}
81+
6382
private class AssertTest
6483
{
6584
private struct DataStruct

ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,12 +1092,17 @@ Func<ILInstruction> ConvertComparison(CallInstruction invocation, ComparisonKind
10921092
}
10931093
return new Call(operatorMethod) { Arguments = { leftInst, rightInst } };
10941094
}
1095-
// A comparison of type parameters has no C# spelling: `v == other` is CS0019 for
1096-
// one, and boxing both operands would change the comparison the tree asks for -
1097-
// Equal on two T is value equality once T is a value type, box identity is not.
1098-
// Leave the tree as the Expression calls that built it.
1099-
if (leftType.Kind == TypeKind.TypeParameter || rightType.Kind == TypeKind.TypeParameter)
1095+
// A comparison of type parameters has no C# spelling unless the parameter is known
1096+
// to be a reference type: `v == other` is CS0019 for an unconstrained one, and boxing
1097+
// both operands would change the comparison the tree asks for - Equal on two T is
1098+
// value equality once T is a value type, box identity is not. A parameter constrained
1099+
// to a reference type compares as one, which is both what the tree asks for and what
1100+
// `t == null` spells. Leave the rest as the Expression calls that built it.
1101+
if (leftType is { Kind: TypeKind.TypeParameter, IsReferenceType: not true }
1102+
|| rightType is { Kind: TypeKind.TypeParameter, IsReferenceType: not true })
1103+
{
11001104
return null;
1105+
}
11011106
var lifting = NullableType.IsNullable(leftType) ? ComparisonLiftingKind.CSharp : ComparisonLiftingKind.None;
11021107
var utype = NullableType.GetUnderlyingType(leftType);
11031108
return new Comp(kind, lifting, utype.GetStackType(), utype.GetSign(), leftInst, rightInst);

0 commit comments

Comments
 (0)