Browse Source

TransformExpressionTrees: Fix built-in comparisons with value types

A hand-built Expression.Equal whose operands are a type parameter has no
lambda to decompile to: `v == other` is CS0019 for a type parameter, and
boxing both operands compiles but compares box identity where the tree
compares values once the parameter is a value type. The conversion now
declines, leaving the Expression calls that built the tree.

Found in EF Core's BoolToTwoValuesConverter<TProvider>.ToBool, which
decompiled to code that does not compile.

Assisted-by: Claude:claude-opus-5:Claude Code
pull/4091/head
Siegfried Pammer 1 week ago
parent
commit
7df4072d1d
  1. 59
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/ExpressionTrees.cs
  2. 6
      ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs

59
ICSharpCode.Decompiler.Tests/TestCases/Correctness/ExpressionTrees.cs

@ -28,6 +28,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -28,6 +28,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
Test();
var twice = GetExpression(Expression.Constant(2)).Compile();
Console.WriteLine(twice(21));
TypeParameterComparisons();
ValueTypeComparisons();
}
static void Test()
@ -43,5 +45,62 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -43,5 +45,62 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "x");
return Expression.Lambda<Func<int, int>>(Expression.Multiply(parameterExpression, factor), parameterExpression);
}
// A comparison of two type parameters, which no lambda can spell: `v == other` is CS0019
// for a type parameter, and boxing both operands compiles but compares box identity,
// where Equal on two T compares values once T is a value type. The int instantiation
// below is what tells those two apart.
static Expression<Func<T, bool>> EqualsValue<T>(T other)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "v");
return Expression.Lambda<Func<T, bool>>(
Expression.Equal(parameterExpression, Expression.Constant(other, typeof(T))), parameterExpression);
}
static Expression<Func<T, bool>> NotEqualsValue<T>(T other)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "v");
return Expression.Lambda<Func<T, bool>>(
Expression.NotEqual(parameterExpression, Expression.Constant(other, typeof(T))), parameterExpression);
}
static void TypeParameterComparisons()
{
object a = new object();
object b = new object();
Console.WriteLine(EqualsValue(a).Compile()(a));
Console.WriteLine(EqualsValue(a).Compile()(b));
Console.WriteLine(NotEqualsValue(a).Compile()(a));
Console.WriteLine(NotEqualsValue(a).Compile()(b));
Console.WriteLine(EqualsValue(42).Compile()(42));
Console.WriteLine(EqualsValue(42).Compile()(43));
}
// The same shape on value types that are not type parameters: decimal and a nullable
// both compare as themselves, and boxing either would turn the comparison into a
// reference comparison of two boxes.
static Expression<Func<decimal, bool>> GreaterThanDecimal(decimal limit)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(decimal), "v");
return Expression.Lambda<Func<decimal, bool>>(
Expression.GreaterThan(parameterExpression, Expression.Constant(limit, typeof(decimal))), parameterExpression);
}
static Expression<Func<int?, bool>> EqualsNullable(int? other)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(int?), "v");
return Expression.Lambda<Func<int?, bool>>(
Expression.Equal(parameterExpression, Expression.Constant(other, typeof(int?))), parameterExpression);
}
static void ValueTypeComparisons()
{
Console.WriteLine(GreaterThanDecimal(1m).Compile()(2m));
Console.WriteLine(GreaterThanDecimal(1m).Compile()(0m));
Console.WriteLine(EqualsNullable(1).Compile()(1));
Console.WriteLine(EqualsNullable(1).Compile()(2));
Console.WriteLine(EqualsNullable(1).Compile()(null));
Console.WriteLine(EqualsNullable(null).Compile()(null));
}
}
}

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

@ -1089,6 +1089,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1089,6 +1089,12 @@ 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)
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