|
|
@ -26,9 +26,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
{ |
|
|
|
{ |
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Nullable lifting gets run in two places:
|
|
|
|
/// Nullable lifting gets run in two places:
|
|
|
|
/// * the usual form looks at an if-else, and runs within the ExpressionTransforms
|
|
|
|
/// * the usual form looks at an if-else, and runs within the ExpressionTransforms.
|
|
|
|
/// * the NullableLiftingBlockTransform handles the cases where Roslyn generates
|
|
|
|
/// * the NullableLiftingBlockTransform handles the cases where Roslyn generates
|
|
|
|
/// two 'ret' statements for the null/non-null cases of a lifted operator.
|
|
|
|
/// two 'ret' statements for the null/non-null cases of a lifted operator.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// The transform handles the following languages constructs:
|
|
|
|
|
|
|
|
/// * lifted conversions
|
|
|
|
|
|
|
|
/// * lifted unary and binary operators
|
|
|
|
|
|
|
|
/// * the ?? operator with type Nullable{T} on the left-hand-side
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
struct NullableLiftingTransform |
|
|
|
struct NullableLiftingTransform |
|
|
|
{ |
|
|
|
{ |
|
|
@ -59,7 +64,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
ILInstruction trueInst = negativeCondition ? ifInst.FalseInst : ifInst.TrueInst; |
|
|
|
ILInstruction trueInst = negativeCondition ? ifInst.FalseInst : ifInst.TrueInst; |
|
|
|
ILInstruction falseInst = negativeCondition ? ifInst.TrueInst : ifInst.FalseInst; |
|
|
|
ILInstruction falseInst = negativeCondition ? ifInst.TrueInst : ifInst.FalseInst; |
|
|
|
var lifted = Lift(ifInst, trueInst, falseInst); |
|
|
|
var lifted = Lift(trueInst, falseInst, ifInst.ILRange); |
|
|
|
if (lifted != null) { |
|
|
|
if (lifted != null) { |
|
|
|
ifInst.ReplaceWith(lifted); |
|
|
|
ifInst.ReplaceWith(lifted); |
|
|
|
return true; |
|
|
|
return true; |
|
|
@ -91,7 +96,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
ILInstruction trueInst = negativeCondition ? elseLeave.Value : thenLeave.Value; |
|
|
|
ILInstruction trueInst = negativeCondition ? elseLeave.Value : thenLeave.Value; |
|
|
|
ILInstruction falseInst = negativeCondition ? thenLeave.Value : elseLeave.Value; |
|
|
|
ILInstruction falseInst = negativeCondition ? thenLeave.Value : elseLeave.Value; |
|
|
|
var lifted = Lift(ifInst, trueInst, falseInst); |
|
|
|
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); |
|
|
@ -130,41 +135,73 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region DoLift
|
|
|
|
#region DoLift
|
|
|
|
ILInstruction Lift(IfInstruction ifInst, ILInstruction trueInst, ILInstruction falseInst) |
|
|
|
ILInstruction Lift(ILInstruction trueInst, ILInstruction falseInst, Interval ilrange) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (!MatchNullableCtor(trueInst, out var utype, out var exprToLift)) |
|
|
|
bool isNullCoalescingWithNonNullableFallback = false; |
|
|
|
return null; |
|
|
|
if (!MatchNullableCtor(trueInst, out var utype, out var exprToLift)) { |
|
|
|
if (!MatchNull(falseInst, utype)) |
|
|
|
isNullCoalescingWithNonNullableFallback = true; |
|
|
|
return null; |
|
|
|
utype = context.TypeSystem.Compilation.FindType(trueInst.ResultType.ToKnownTypeCode()); |
|
|
|
|
|
|
|
exprToLift = trueInst; |
|
|
|
|
|
|
|
if (nullableVars.Count == 1 && exprToLift.MatchLdLoc(nullableVars[0])) { |
|
|
|
|
|
|
|
// v.HasValue ? ldloc v : fallback
|
|
|
|
|
|
|
|
// => v ?? fallback
|
|
|
|
|
|
|
|
context.Step("v.HasValue ? v : fallback => v ?? fallback", trueInst); |
|
|
|
|
|
|
|
return new NullCoalescingInstruction(NullCoalescingKind.Nullable, trueInst, falseInst) { |
|
|
|
|
|
|
|
UnderlyingResultType = NullableType.GetUnderlyingType(nullableVars[0].Type).GetStackType(), |
|
|
|
|
|
|
|
ILRange = ilrange |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
ILInstruction lifted; |
|
|
|
ILInstruction lifted; |
|
|
|
if (nullableVars.Count == 1 && MatchGetValueOrDefault(exprToLift, nullableVars[0])) { |
|
|
|
if (nullableVars.Count == 1 && MatchGetValueOrDefault(exprToLift, nullableVars[0])) { |
|
|
|
// v != null ? call GetValueOrDefault(ldloca v) : null
|
|
|
|
// v.HasValue ? call GetValueOrDefault(ldloca v) : fallback
|
|
|
|
// => conv.nop.lifted(ldloc v)
|
|
|
|
// => conv.nop.lifted(ldloc v) ?? fallback
|
|
|
|
// This case is handled separately from DoLift() because
|
|
|
|
// This case is handled separately from DoLift() because
|
|
|
|
// that doesn't introduce nop-conversions.
|
|
|
|
// that doesn't introduce nop-conversions.
|
|
|
|
context.Step("if => conv.nop.lifted", ifInst); |
|
|
|
context.Step("v.HasValue ? v.GetValueOrDefault() : fallback => v ?? fallback", trueInst); |
|
|
|
var inputUType = NullableType.GetUnderlyingType(nullableVars[0].Type); |
|
|
|
var inputUType = NullableType.GetUnderlyingType(nullableVars[0].Type); |
|
|
|
lifted = new Conv( |
|
|
|
lifted = new LdLoc(nullableVars[0]); |
|
|
|
new LdLoc(nullableVars[0]), |
|
|
|
if (!inputUType.Equals(utype) && utype.ToPrimitiveType() != PrimitiveType.None) { |
|
|
|
inputUType.GetStackType(), inputUType.GetSign(), utype.ToPrimitiveType(), |
|
|
|
// While the ILAst allows implicit conversions between short and int
|
|
|
|
checkForOverflow: false, |
|
|
|
// (because both map to I4); it does not allow implicit conversions
|
|
|
|
isLifted: true |
|
|
|
// between short? and int? (structs of different types).
|
|
|
|
) { |
|
|
|
// So use 'conv.nop.lifted' to allow the conversion.
|
|
|
|
ILRange = ifInst.ILRange |
|
|
|
lifted = new Conv( |
|
|
|
}; |
|
|
|
lifted, |
|
|
|
|
|
|
|
inputUType.GetStackType(), inputUType.GetSign(), utype.ToPrimitiveType(), |
|
|
|
|
|
|
|
checkForOverflow: false, |
|
|
|
|
|
|
|
isLifted: true |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
ILRange = ilrange |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
context.Step("NullableLiftingTransform.DoLift", ifInst); |
|
|
|
context.Step("NullableLiftingTransform.DoLift", trueInst); |
|
|
|
BitSet bits; |
|
|
|
BitSet bits; |
|
|
|
(lifted, bits) = DoLift(exprToLift); |
|
|
|
(lifted, bits) = DoLift(exprToLift); |
|
|
|
if (lifted != null && !bits.All(0, nullableVars.Count)) { |
|
|
|
if (lifted == null) { |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!bits.All(0, nullableVars.Count)) { |
|
|
|
// don't lift if a nullableVar doesn't contribute to the result
|
|
|
|
// don't lift if a nullableVar doesn't contribute to the result
|
|
|
|
lifted = null; |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (lifted != null) { |
|
|
|
|
|
|
|
Debug.Assert(lifted is ILiftableInstruction liftable && liftable.IsLifted |
|
|
|
Debug.Assert(lifted is ILiftableInstruction liftable && liftable.IsLifted |
|
|
|
&& liftable.UnderlyingResultType == exprToLift.ResultType); |
|
|
|
&& liftable.UnderlyingResultType == exprToLift.ResultType); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (isNullCoalescingWithNonNullableFallback) { |
|
|
|
|
|
|
|
lifted = new NullCoalescingInstruction(NullCoalescingKind.NullableWithValueFallback, lifted, falseInst) { |
|
|
|
|
|
|
|
UnderlyingResultType = exprToLift.ResultType, |
|
|
|
|
|
|
|
ILRange = ilrange |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} else if (!MatchNull(falseInst, utype)) { |
|
|
|
|
|
|
|
// Normal lifting, but the falseInst isn't `default(utype?)`
|
|
|
|
|
|
|
|
// => use the `??` operator to provide the fallback value.
|
|
|
|
|
|
|
|
lifted = new NullCoalescingInstruction(NullCoalescingKind.Nullable, lifted, falseInst) { |
|
|
|
|
|
|
|
UnderlyingResultType = exprToLift.ResultType, |
|
|
|
|
|
|
|
ILRange = ilrange |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
return lifted; |
|
|
|
return lifted; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|