Browse Source

[nullables] Add support for equality comparisons.

pull/870/head
Daniel Grunwald 8 years ago
parent
commit
8701640ca7
  1. 41
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs
  2. 12
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 70
      ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

41
ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs

@ -505,6 +505,22 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -505,6 +505,22 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine(2m + a);
}
public static void CompareWithImplictCast(int? a, long? b)
{
if (a < b) {
Console.WriteLine();
}
if (a == b) {
Console.WriteLine();
}
if (a < 10L) {
Console.WriteLine();
}
if (a == 10L) {
Console.WriteLine();
}
}
public static void CompareWithSignChange(int? a, int? b)
{
if ((uint?)a < (uint?)b) {
@ -654,11 +670,36 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -654,11 +670,36 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return a == b;
}
public static bool RetEqConv(long? a, int? b)
{
return a == b;
}
public static bool RetEqConst(long? a)
{
return a == 10;
}
public static bool RetIneqConst(long? a)
{
return a != 10;
}
public static bool RetLt(int? a, int? b)
{
return a < b;
}
public static bool RetLtConst(int? a)
{
return a < 10;
}
public static bool RetLtConv(long? a, int? b)
{
return a < b;
}
public static bool RetNotLt(int? a, int? b)
{
return !(a < b);

12
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -493,13 +493,16 @@ namespace ICSharpCode.Decompiler.CSharp @@ -493,13 +493,16 @@ namespace ICSharpCode.Decompiler.CSharp
var rr = resolver.ResolveBinaryOperator(inst.Kind.ToBinaryOperatorType(), left.ResolveResult, right.ResolveResult)
as OperatorResolveResult;
if (rr == null || rr.IsError || rr.UserDefinedOperatorMethod != null
|| rr.Operands[0].Type.GetStackType() != inst.InputType)
|| NullableType.GetUnderlyingType(rr.Operands[0].Type).GetStackType() != inst.InputType)
{
IType targetType;
if (inst.InputType == StackType.O) {
targetType = compilation.FindType(KnownTypeCode.Object);
} else {
targetType = TypeUtils.GetLargerType(left.Type, right.Type);
targetType = TypeUtils.GetLargerType(NullableType.GetUnderlyingType(left.Type), NullableType.GetUnderlyingType(right.Type));
}
if (inst.IsLifted) {
targetType = NullableType.Create(compilation, targetType);
}
if (targetType.Equals(left.Type)) {
right = right.ConvertTo(targetType, this);
@ -509,7 +512,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -509,7 +512,7 @@ namespace ICSharpCode.Decompiler.CSharp
rr = resolver.ResolveBinaryOperator(inst.Kind.ToBinaryOperatorType(),
left.ResolveResult, right.ResolveResult) as OperatorResolveResult;
if (rr == null || rr.IsError || rr.UserDefinedOperatorMethod != null
|| rr.Operands[0].Type.GetStackType() != inst.InputType)
|| NullableType.GetUnderlyingType(rr.Operands[0].Type).GetStackType() != inst.InputType)
{
// If converting one input wasn't sufficient, convert both:
left = left.ConvertTo(targetType, this);
@ -1473,6 +1476,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1473,6 +1476,7 @@ namespace ICSharpCode.Decompiler.CSharp
if (!expr.ResolveResult.IsCompileTimeConstant) {
return expr;
}
type = NullableType.GetUnderlyingType(type);
if (type.IsKnownType(KnownTypeCode.Boolean)
&& (object.Equals(expr.ResolveResult.ConstantValue, 0) || object.Equals(expr.ResolveResult.ConstantValue, 1))) {
return expr.ConvertToBoolean(this);
@ -1489,7 +1493,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1489,7 +1493,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
var value = Translate(inst.ValueInst);
var fallback = Translate(inst.FallbackInst);
fallback = AdjustConstantExpressionToType(fallback, NullableType.GetUnderlyingType(value.Type));
fallback = AdjustConstantExpressionToType(fallback, value.Type);
var rr = resolver.ResolveBinaryOperator(BinaryOperatorType.NullCoalescing, value.ResolveResult, fallback.ResolveResult);
if (rr.IsError) {
IType targetType;

70
ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

@ -33,6 +33,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -33,6 +33,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// The transform handles the following languages constructs:
/// * lifted conversions
/// * lifted unary and binary operators
/// * lifted comparisons
/// * the ?? operator with type Nullable{T} on the left-hand-side
/// </summary>
struct NullableLiftingTransform
@ -125,16 +126,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -125,16 +126,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// => normal lifting
return LiftNormal(trueInst, falseInst, ilrange: ifInst.ILRange);
}
if (condition is Comp comp && !comp.IsLifted && !comp.Kind.IsEqualityOrInequality()) {
if (condition is Comp comp && !comp.IsLifted) {
// This might be a C#-style lifted comparison
// (C# checks the underlying value before checking the HasValue bits)
if (falseInst.MatchLdcI4(0) && AnalyzeCondition(trueInst)) {
// comp(lhs, rhs) ? (v1 != null && ... && vn != null) : false
// => comp.lifted[C#](lhs, rhs)
return LiftCSharpComparison(comp, comp.Kind);
} else if (trueInst.MatchLdcI4(0) && AnalyzeCondition(falseInst)) {
// comp(lhs, rhs) ? false : (v1 != null && ... && vn != null)
return LiftCSharpComparison(comp, comp.Kind.Negate());
if (comp.Kind.IsEqualityOrInequality()) {
// for equality/inequality, the HasValue bits must also compare equal/inequal
if (falseInst.MatchLdcI4(0)) {
return LiftCSharpEqualityComparison(comp, comp.Kind, trueInst);
} else if (trueInst.MatchLdcI4(0)) {
return LiftCSharpEqualityComparison(comp, comp.Kind.Negate(), falseInst);
}
} else {
// Not (in)equality, but one of < <= > >=.
// Returns false unless all HasValue bits are true.
if (falseInst.MatchLdcI4(0) && AnalyzeCondition(trueInst)) {
// comp(lhs, rhs) ? (v1 != null && ... && vn != null) : false
// => comp.lifted[C#](lhs, rhs)
return LiftCSharpComparison(comp, comp.Kind);
} else if (trueInst.MatchLdcI4(0) && AnalyzeCondition(falseInst)) {
// comp(lhs, rhs) ? false : (v1 != null && ... && vn != null)
return LiftCSharpComparison(comp, comp.Kind.Negate());
}
}
}
return null;
@ -148,10 +160,50 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -148,10 +160,50 @@ namespace ICSharpCode.Decompiler.IL.Transforms
b = tmp;
}
Comp LiftCSharpEqualityComparison(Comp valueComp, ComparisonKind newComparisonKind, ILInstruction hasValueTest)
{
Debug.Assert(newComparisonKind.IsEqualityOrInequality());
// The HasValue comparison must be the same operator as the Value comparison.
if (hasValueTest is Comp hasValueComp) {
// Comparing two nullables: HasValue comparison must be the same operator as the Value comparison
if (hasValueComp.Kind != newComparisonKind)
return null;
if (!MatchHasValueCall(hasValueComp.Left, out var leftVar))
return null;
if (!MatchHasValueCall(hasValueComp.Right, out var rightVar))
return null;
nullableVars = new List<ILVariable> { leftVar };
var (left, leftBits) = DoLift(valueComp.Left);
nullableVars[0] = rightVar;
var (right, rightBits) = DoLift(valueComp.Right);
if (left != null && right != null && leftBits[0] && rightBits[0]
&& SemanticHelper.IsPure(left.Flags) && SemanticHelper.IsPure(right.Flags)
) {
context.Step("NullableLiftingTransform: C# (in)equality comparison", valueComp);
return new Comp(newComparisonKind, ComparisonLiftingKind.CSharp, valueComp.InputType, valueComp.Sign, left, right);
}
} else if (newComparisonKind == ComparisonKind.Equality && MatchHasValueCall(hasValueTest, out var v)) {
// Comparing nullable with non-nullable -> we can fall back to the normal comparison code.
nullableVars = new List<ILVariable> { v };
return LiftCSharpComparison(valueComp, newComparisonKind);
} else if (newComparisonKind == ComparisonKind.Inequality
&& hasValueTest.MatchLogicNot(out var arg)
&& MatchHasValueCall(arg, out v)
) {
// Comparing nullable with non-nullable -> we can fall back to the normal comparison code.
nullableVars = new List<ILVariable> { v };
return LiftCSharpComparison(valueComp, newComparisonKind);
}
return null;
}
/// <summary>
/// Lift a C# comparison.
/// This method cannot be used for (in)equality comparisons where both sides are nullable
/// (these special cases are handled in LiftCSharpEqualityComparison instead).
///
/// The output instructions should evaluate to <c>false</c> when any of the <c>nullableVars</c> is <c>null</c>.
/// The output instructions should evaluate to <c>false</c> when any of the <c>nullableVars</c> is <c>null</c>
/// (except for newComparisonKind==Inequality, where this case should evaluate to <c>true</c> instead).
/// Otherwise, the output instruction should evaluate to the same value as the input instruction.
/// The output instruction should have the same side-effects (incl. exceptions being thrown) as the input instruction.
/// This means unlike LiftNormal(), we cannot rely on the input instruction not being evaluated if

Loading…
Cancel
Save