Browse Source

[nullables] Lifting for non-equality comparisons.

pull/870/head
Daniel Grunwald 8 years ago
parent
commit
21ef967d4d
  1. 15
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs
  2. 11
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 8
      ICSharpCode.Decompiler/IL/Instructions.cs
  4. 8
      ICSharpCode.Decompiler/IL/Instructions.tt
  5. 2
      ICSharpCode.Decompiler/IL/Instructions/BinaryNumericInstruction.cs
  6. 71
      ICSharpCode.Decompiler/IL/Instructions/Comp.cs
  7. 12
      ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
  8. 9
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  9. 104
      ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

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

@ -648,6 +648,21 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine(x() + a); Console.WriteLine(x() + a);
(new TS?[0])[0] += x(); (new TS?[0])[0] += x();
} }
public static bool RetEq(int? a, int? b)
{
return a == b;
}
public static bool RetLt(int? a, int? b)
{
return a < b;
}
public static bool RetNotLt(int? a, int? b)
{
return !(a < b);
}
} }
// dummy structure for testing custom operators // dummy structure for testing custom operators

11
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -423,6 +423,9 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitComp(Comp inst, TranslationContext context) protected internal override TranslatedExpression VisitComp(Comp inst, TranslationContext context)
{ {
if (inst.LiftingKind == ComparisonLiftingKind.ThreeValuedLogic) {
return ErrorExpression("Nullable comparisons with three-valued-logic not supported in C#");
}
if (inst.Kind.IsEqualityOrInequality()) { if (inst.Kind.IsEqualityOrInequality()) {
bool negateOutput; bool negateOutput;
var result = TranslateCeq(inst, out negateOutput); var result = TranslateCeq(inst, out negateOutput);
@ -541,8 +544,12 @@ namespace ICSharpCode.Decompiler.CSharp
break; break;
} }
if (inputType != KnownTypeCode.None) { if (inputType != KnownTypeCode.None) {
left = left.ConvertTo(compilation.FindType(inputType), this); IType targetType = compilation.FindType(inputType);
right = right.ConvertTo(compilation.FindType(inputType), this); if (inst.IsLifted) {
targetType = NullableType.Create(compilation, targetType);
}
left = left.ConvertTo(targetType, this);
right = right.ConvertTo(targetType, this);
} }
var op = inst.Kind.ToBinaryOperatorType(); var op = inst.Kind.ToBinaryOperatorType();
return new BinaryOperatorExpression(left.Expression, op, right.Expression) return new BinaryOperatorExpression(left.Expression, op, right.Expression)

8
ICSharpCode.Decompiler/IL/Instructions.cs

@ -881,7 +881,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match) protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{ {
var o = other as BinaryNumericInstruction; var o = other as BinaryNumericInstruction;
return o != null && this.Left.PerformMatch(o.Left, ref match) && this.Right.PerformMatch(o.Right, ref match) && CheckForOverflow == o.CheckForOverflow && Sign == o.Sign && Operator == o.Operator; return o != null && this.Left.PerformMatch(o.Left, ref match) && this.Right.PerformMatch(o.Right, ref match) && CheckForOverflow == o.CheckForOverflow && Sign == o.Sign && Operator == o.Operator && IsLifted == o.IsLifted;
} }
} }
} }
@ -1757,7 +1757,7 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>Comparison. The inputs must be both integers; or both floats; or both object references. Object references can only be compared for equality or inequality. Floating-point comparisons evaluate to 0 (false) when an input is NaN, except for 'NaN != NaN' which evaluates to 1 (true).</summary> /// <summary>Comparison. The inputs must be both integers; or both floats; or both object references. Object references can only be compared for equality or inequality. Floating-point comparisons evaluate to 0 (false) when an input is NaN, except for 'NaN != NaN' which evaluates to 1 (true).</summary>
public sealed partial class Comp : BinaryInstruction public sealed partial class Comp : BinaryInstruction
{ {
public override StackType ResultType { get { return StackType.I4; } }
public override void AcceptVisitor(ILVisitor visitor) public override void AcceptVisitor(ILVisitor visitor)
{ {
visitor.VisitComp(this); visitor.VisitComp(this);
@ -1773,7 +1773,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match) protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{ {
var o = other as Comp; var o = other as Comp;
return o != null && this.Left.PerformMatch(o.Left, ref match) && this.Right.PerformMatch(o.Right, ref match) && this.Kind == o.Kind && this.Sign == o.Sign; return o != null && this.Left.PerformMatch(o.Left, ref match) && this.Right.PerformMatch(o.Right, ref match) && this.Kind == o.Kind && this.Sign == o.Sign && this.LiftingKind == o.LiftingKind;
} }
} }
} }
@ -1881,7 +1881,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match) protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{ {
var o = other as Conv; var o = other as Conv;
return o != null && this.Argument.PerformMatch(o.Argument, ref match) && CheckForOverflow == o.CheckForOverflow && Kind == o.Kind && InputSign == o.InputSign && TargetType == o.TargetType; return o != null && this.Argument.PerformMatch(o.Argument, ref match) && CheckForOverflow == o.CheckForOverflow && Kind == o.Kind && InputSign == o.InputSign && TargetType == o.TargetType && IsLifted == o.IsLifted;
} }
} }
} }

8
ICSharpCode.Decompiler/IL/Instructions.tt

@ -67,7 +67,7 @@
ResultType("I4"), Unary), ResultType("I4"), Unary),
new OpCode("binary", "Common instruction for add, sub, mul, div, rem, bit.and, bit.or, bit.xor, shl and shr.", new OpCode("binary", "Common instruction for add, sub, mul, div, rem, bit.and, bit.or, bit.xor, shl and shr.",
CustomClassName("BinaryNumericInstruction"), Binary, CustomWriteTo, CustomConstructor, CustomComputeFlags, CustomClassName("BinaryNumericInstruction"), Binary, CustomWriteTo, CustomConstructor, CustomComputeFlags,
MatchCondition("CheckForOverflow == o.CheckForOverflow && Sign == o.Sign && Operator == o.Operator")), MatchCondition("CheckForOverflow == o.CheckForOverflow && Sign == o.Sign && Operator == o.Operator && IsLifted == o.IsLifted")),
new OpCode("compound", "Common instruction for compound assignments.", new OpCode("compound", "Common instruction for compound assignments.",
CustomClassName("CompoundAssignmentInstruction"), CustomConstructor, CustomComputeFlags, CustomClassName("CompoundAssignmentInstruction"), CustomConstructor, CustomComputeFlags,
MayThrow, CustomArguments("target", "value"), HasTypeOperand, ResultType("type.GetStackType()"), CustomWriteTo, MayThrow, CustomArguments("target", "value"), HasTypeOperand, ResultType("type.GetStackType()"), CustomWriteTo,
@ -127,8 +127,8 @@
+ "Object references can only be compared for equality or inequality. " + "Object references can only be compared for equality or inequality. "
+ "Floating-point comparisons evaluate to 0 (false) when an input is NaN, except for 'NaN != NaN' which " + "Floating-point comparisons evaluate to 0 (false) when an input is NaN, except for 'NaN != NaN' which "
+ "evaluates to 1 (true).", + "evaluates to 1 (true).",
Binary, CustomConstructor, CustomWriteTo, ResultType("I4"), Binary, CustomConstructor, CustomWriteTo,
MatchCondition("this.Kind == o.Kind && this.Sign == o.Sign")), MatchCondition("this.Kind == o.Kind && this.Sign == o.Sign && this.LiftingKind == o.LiftingKind")),
new OpCode("call", "Non-virtual method call.", Call), new OpCode("call", "Non-virtual method call.", Call),
new OpCode("callvirt", "Virtual method call.", new OpCode("callvirt", "Virtual method call.",
CustomClassName("CallVirt"), Call), CustomClassName("CallVirt"), Call),
@ -136,7 +136,7 @@
Unary, MayThrow, VoidResult), Unary, MayThrow, VoidResult),
new OpCode("conv", "Numeric cast.", new OpCode("conv", "Numeric cast.",
Unary, CustomConstructor, Unary, CustomConstructor,
MatchCondition("CheckForOverflow == o.CheckForOverflow && Kind == o.Kind && InputSign == o.InputSign && TargetType == o.TargetType")), MatchCondition("CheckForOverflow == o.CheckForOverflow && Kind == o.Kind && InputSign == o.InputSign && TargetType == o.TargetType && IsLifted == o.IsLifted")),
new OpCode("ldloc", "Loads the value of a local variable. (ldarg/ldloc)", new OpCode("ldloc", "Loads the value of a local variable. (ldarg/ldloc)",
CustomClassName("LdLoc"), NoArguments, HasVariableOperand("Load"), ResultType("variable.StackType")), CustomClassName("LdLoc"), NoArguments, HasVariableOperand("Load"), ResultType("variable.StackType")),
new OpCode("ldloca", "Loads the address of a local variable. (ldarga/ldloca)", new OpCode("ldloca", "Loads the address of a local variable. (ldarga/ldloca)",

2
ICSharpCode.Decompiler/IL/Instructions/BinaryNumericInstruction.cs

@ -65,7 +65,7 @@ namespace ICSharpCode.Decompiler.IL
/// <remarks> /// <remarks>
/// A lifted binary operation allows its arguments to be a value of type Nullable{T}, where /// A lifted binary operation allows its arguments to be a value of type Nullable{T}, where
/// T.GetStackType() == [Left|Right]InputType. /// T.GetStackType() == [Left|Right]InputType.
/// If both input values is non-null: /// If both input values are non-null:
/// * they are sign/zero-extended to the corresponding InputType (based on T's sign) /// * they are sign/zero-extended to the corresponding InputType (based on T's sign)
/// * the underlying numeric operator is applied /// * the underlying numeric operator is applied
/// * the result is wrapped in a Nullable{UnderlyingResultType}. /// * the result is wrapped in a Nullable{UnderlyingResultType}.

71
ICSharpCode.Decompiler/IL/Instructions/Comp.cs

@ -85,7 +85,35 @@ namespace ICSharpCode.Decompiler.IL
} }
} }
partial class Comp public enum ComparisonLiftingKind
{
/// <summary>
/// Not a lifted comparison.
/// </summary>
None,
/// <summary>
/// C#-style lifted comparison:
/// * operands that have a ResultType != this.InputType are expected to return a value of
/// type Nullable{T}, where T.GetStackType() == this.InputType.
/// * if both operands are <c>null</c>, equality comparisons evaluate to 1, all other comparisons to 0.
/// * if one operand is <c>null</c>, inequality comparisons evaluate to 1, all other comparisons to 0.
/// * if neither operand is <c>null</c>, the underlying comparison is performed.
///
/// Note that even though C#-style lifted comparisons set IsLifted=true,
/// the ResultType remains I4 as with normal comparisons.
/// </summary>
CSharp,
/// <summary>
/// SQL-style lifted comparison: works like a lifted binary numeric instruction,
/// that is, if any input operand is <c>null</c>, the comparison evaluates to <c>null</c>.
/// </summary>
/// <remarks>
/// This lifting kind is currently not used.
/// </remarks>
ThreeValuedLogic
}
partial class Comp : ILiftableInstruction
{ {
ComparisonKind kind; ComparisonKind kind;
@ -97,12 +125,13 @@ namespace ICSharpCode.Decompiler.IL
} }
} }
public readonly ComparisonLiftingKind LiftingKind;
/// <summary> /// <summary>
/// Gets the stack type of the comparison inputs. /// Gets the stack type of the comparison inputs.
/// For lifted comparisons, this is the underlying input type.
/// </summary> /// </summary>
public StackType InputType { public StackType InputType;
get { return Left.ResultType; }
}
/// <summary> /// <summary>
/// If this is an integer comparison, specifies the sign used to interpret the integers. /// If this is an integer comparison, specifies the sign used to interpret the integers.
@ -112,10 +141,36 @@ namespace ICSharpCode.Decompiler.IL
public Comp(ComparisonKind kind, Sign sign, ILInstruction left, ILInstruction right) : base(OpCode.Comp, left, right) public Comp(ComparisonKind kind, Sign sign, ILInstruction left, ILInstruction right) : base(OpCode.Comp, left, right)
{ {
this.kind = kind; this.kind = kind;
this.LiftingKind = ComparisonLiftingKind.None;
this.InputType = left.ResultType;
this.Sign = sign; this.Sign = sign;
Debug.Assert(left.ResultType == right.ResultType); Debug.Assert(left.ResultType == right.ResultType);
} }
public Comp(ComparisonKind kind, ComparisonLiftingKind lifting, StackType inputType, Sign sign, ILInstruction left, ILInstruction right) : base(OpCode.Comp, left, right)
{
this.kind = kind;
this.LiftingKind = lifting;
this.InputType = inputType;
this.Sign = sign;
}
public override StackType ResultType => LiftingKind == ComparisonLiftingKind.ThreeValuedLogic ? StackType.O : StackType.I4;
public bool IsLifted => LiftingKind != ComparisonLiftingKind.None;
public StackType UnderlyingResultType => StackType.I4;
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);
if (LiftingKind == ComparisonLiftingKind.None) {
Debug.Assert(Left.ResultType == InputType);
Debug.Assert(Right.ResultType == InputType);
} else {
Debug.Assert(Left.ResultType == InputType || Left.ResultType == StackType.O);
Debug.Assert(Right.ResultType == InputType || Right.ResultType == StackType.O);
}
}
public override void WriteTo(ITextOutput output) public override void WriteTo(ITextOutput output)
{ {
output.Write(OpCode); output.Write(OpCode);
@ -127,6 +182,14 @@ namespace ICSharpCode.Decompiler.IL
output.Write(".unsigned"); output.Write(".unsigned");
break; break;
} }
switch (LiftingKind) {
case ComparisonLiftingKind.CSharp:
output.Write(".lifted[C#]");
break;
case ComparisonLiftingKind.ThreeValuedLogic:
output.Write(".lifted[3VL]");
break;
}
output.Write('('); output.Write('(');
Left.WriteTo(output); Left.WriteTo(output);
output.Write(' '); output.Write(' ');

12
ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs

@ -707,7 +707,19 @@ namespace ICSharpCode.Decompiler.IL
public interface ILiftableInstruction public interface ILiftableInstruction
{ {
/// <summary>
/// Gets whether the instruction was lifted; that is, whether is accepts
/// potentially nullable arguments.
/// </summary>
bool IsLifted { get; } bool IsLifted { get; }
/// <summary>
/// If the instruction is lifted and returns a nullable result,
/// gets the underlying result type.
///
/// Note that not all lifted instructions return a nullable result:
/// C# comparisons always return a bool!
/// </summary>
StackType UnderlyingResultType { get; } StackType UnderlyingResultType { get; }
} }
} }

9
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -55,6 +55,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
protected internal override void VisitComp(Comp inst) protected internal override void VisitComp(Comp inst)
{ {
base.VisitComp(inst); base.VisitComp(inst);
if (inst.IsLifted) {
return;
}
if (inst.Right.MatchLdNull()) { if (inst.Right.MatchLdNull()) {
if (inst.Kind == ComparisonKind.GreaterThan) { if (inst.Kind == ComparisonKind.GreaterThan) {
context.Step("comp(left > ldnull) => comp(left != ldnull)", inst); context.Step("comp(left > ldnull) => comp(left != ldnull)", inst);
@ -84,6 +87,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// => comp(ldlen.i4 array > ldc.i4 0) // => comp(ldlen.i4 array > ldc.i4 0)
// This is a special case where the C# compiler doesn't generate conv.i4 after ldlen. // This is a special case where the C# compiler doesn't generate conv.i4 after ldlen.
context.Step("comp(ldlen.i4 array > ldc.i4 0)", inst); context.Step("comp(ldlen.i4 array > ldc.i4 0)", inst);
inst.InputType = StackType.I4;
inst.Left.ReplaceWith(new LdLen(StackType.I4, array) { ILRange = inst.Left.ILRange }); inst.Left.ReplaceWith(new LdLen(StackType.I4, array) { ILRange = inst.Left.ILRange });
inst.Right = rightWithoutConv; inst.Right = rightWithoutConv;
} }
@ -143,9 +147,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
arg.AddILRange(inst.Argument.ILRange); arg.AddILRange(inst.Argument.ILRange);
inst.ReplaceWith(arg); inst.ReplaceWith(arg);
arg.AcceptVisitor(this); arg.AcceptVisitor(this);
} else if (inst.Argument is Comp) { } else if (inst.Argument is Comp comp) {
Comp comp = (Comp)inst.Argument; if ((comp.InputType != StackType.F && !comp.IsLifted) || comp.Kind.IsEqualityOrInequality()) {
if (comp.InputType != StackType.F || comp.Kind.IsEqualityOrInequality()) {
context.Step("push negation into comparison", inst); context.Step("push negation into comparison", inst);
comp.Kind = comp.Kind.Negate(); comp.Kind = comp.Kind.Negate();
comp.AddILRange(inst.ILRange); comp.AddILRange(inst.ILRange);

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

@ -55,16 +55,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
if (!context.Settings.LiftNullables) if (!context.Settings.LiftNullables)
return false; return false;
// Detect pattern: var lifted = Lift(ifInst, ifInst.TrueInst, ifInst.FalseInst);
// if (condition)
// newobj Nullable<utype>..ctor(exprToLift)
// else
// default.value System.Nullable<utype>
if (!AnalyzeTopLevelCondition(ifInst.Condition, out bool negativeCondition))
return false;
ILInstruction trueInst = negativeCondition ? ifInst.FalseInst : ifInst.TrueInst;
ILInstruction falseInst = negativeCondition ? ifInst.TrueInst : ifInst.FalseInst;
var lifted = Lift(trueInst, falseInst, ifInst.ILRange);
if (lifted != null) { if (lifted != null) {
ifInst.ReplaceWith(lifted); ifInst.ReplaceWith(lifted);
return true; return true;
@ -76,6 +67,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
if (!context.Settings.LiftNullables) if (!context.Settings.LiftNullables)
return false; return false;
/// e.g.:
// if (!condition) Block { // if (!condition) Block {
// leave IL_0000 (default.value System.Nullable`1[[System.Int64]]) // leave IL_0000 (default.value System.Nullable`1[[System.Int64]])
// } // }
@ -92,11 +84,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (elseLeave.TargetContainer != thenLeave.TargetContainer) if (elseLeave.TargetContainer != thenLeave.TargetContainer)
return false; return false;
if (!AnalyzeTopLevelCondition(ifInst.Condition, out bool negativeCondition)) var lifted = Lift(ifInst, thenLeave.Value, elseLeave.Value);
return false;
ILInstruction trueInst = negativeCondition ? elseLeave.Value : thenLeave.Value;
ILInstruction falseInst = negativeCondition ? thenLeave.Value : elseLeave.Value;
var lifted = Lift(trueInst, falseInst, ifInst.ILRange);
if (lifted != null) { if (lifted != null) {
thenLeave.Value = lifted; thenLeave.Value = lifted;
ifInst.ReplaceWith(thenLeave); ifInst.ReplaceWith(thenLeave);
@ -108,16 +96,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
#endregion #endregion
#region AnalyzeCondition #region AnalyzeCondition
bool AnalyzeTopLevelCondition(ILInstruction condition, out bool negativeCondition)
{
negativeCondition = false;
while (condition.MatchLogicNot(out var arg)) {
condition = arg;
negativeCondition = !negativeCondition;
}
return AnalyzeCondition(condition);
}
bool AnalyzeCondition(ILInstruction condition) bool AnalyzeCondition(ILInstruction condition)
{ {
if (MatchHasValueCall(condition, out var v)) { if (MatchHasValueCall(condition, out var v)) {
@ -134,7 +112,79 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
#endregion #endregion
#region DoLift #region Lift / DoLift
ILInstruction Lift(IfInstruction ifInst, ILInstruction trueInst, ILInstruction falseInst)
{
ILInstruction condition = ifInst.Condition;
while (condition.MatchLogicNot(out var arg)) {
condition = arg;
Swap(ref trueInst, ref falseInst);
}
if (AnalyzeCondition(condition)) {
// (v1 != null && ... && vn != null) ? trueInst : falseInst
// => normal lifting
return LiftNormal(trueInst, falseInst, ilrange: ifInst.ILRange);
}
if (condition is Comp comp && !comp.IsLifted && !comp.Kind.IsEqualityOrInequality()) {
// 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());
}
}
return null;
}
static void Swap<T>(ref T a, ref T b)
{
T tmp = a;
a = b;
b = tmp;
}
/// <summary>
/// Lift a C# comparison.
///
/// The output instructions should evaluate to <c>false</c> when any of the <c>nullableVars</c> is <c>null</c>.
/// 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
/// a variable is <c>null</c>.
/// </summary>
Comp LiftCSharpComparison(Comp comp, ComparisonKind newComparisonKind)
{
var (left, leftBits) = DoLift(comp.Left);
var (right, rightBits) = DoLift(comp.Right);
if (left != null && right == null && SemanticHelper.IsPure(comp.Right.Flags)) {
// Embed non-nullable pure expression in lifted expression.
right = comp.Right.Clone();
}
if (left == null && right != null && SemanticHelper.IsPure(comp.Left.Flags)) {
// Embed non-nullable pure expression in lifted expression.
left = comp.Left.Clone();
}
// due to the restrictions on side effects, we only allow instructions that are pure after lifting.
// (we can't check this before lifting due to the calls to GetValueOrDefault())
if (left != null && right != null && SemanticHelper.IsPure(left.Flags) && SemanticHelper.IsPure(right.Flags)) {
var bits = leftBits ?? rightBits;
if (rightBits != null)
bits.UnionWith(rightBits);
if (!bits.All(0, nullableVars.Count)) {
// don't lift if a nullableVar doesn't contribute to the result
return null;
}
context.Step("NullableLiftingTransform: C# comparison", comp);
return new Comp(newComparisonKind, ComparisonLiftingKind.CSharp, comp.InputType, comp.Sign, left, right);
}
return null;
}
/// <summary> /// <summary>
/// Performs nullable lifting. /// Performs nullable lifting.
/// ///
@ -143,7 +193,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// where the v1,...,vn are the <c>this.nullableVars</c>. /// where the v1,...,vn are the <c>this.nullableVars</c>.
/// If lifting fails, returns <c>null</c>. /// If lifting fails, returns <c>null</c>.
/// </summary> /// </summary>
ILInstruction Lift(ILInstruction trueInst, ILInstruction falseInst, Interval ilrange) ILInstruction LiftNormal(ILInstruction trueInst, ILInstruction falseInst, Interval ilrange)
{ {
bool isNullCoalescingWithNonNullableFallback = false; bool isNullCoalescingWithNonNullableFallback = false;
if (!MatchNullableCtor(trueInst, out var utype, out var exprToLift)) { if (!MatchNullableCtor(trueInst, out var utype, out var exprToLift)) {

Loading…
Cancel
Save