From d949d4e638959711ee0b174a580ce43958552616 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 10 Dec 2017 21:39:06 +0100 Subject: [PATCH] Fix #991: explicit represent T->T? conversion in lifted operator calls. --- .../IL/Transforms/ILInlining.cs | 4 ++ .../IL/Transforms/NullableLiftingTransform.cs | 52 ++++++++++++++++--- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 21db57801..d9a5f75ac 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -282,6 +282,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms } var parent = loadInst.Parent; + if (NullableLiftingTransform.MatchNullableCtor(parent, out _, out _)) { + // inline into nullable ctor call in lifted operator + parent = parent.Parent; + } if (parent is ILiftableInstruction liftable && liftable.IsLifted) { return true; // inline into lifted operators } diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs index 7498f857a..f6ebe5587 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs @@ -334,6 +334,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms public ILInstruction Left; public ILInstruction Right; + public IType LeftExpectedType { + get { + if (Instruction is Call call) { + return call.Method.Parameters[0].Type; + } else { + return SpecialType.UnknownType; + } + } + } + + public IType RightExpectedType { + get { + if (Instruction is Call call) { + return call.Method.Parameters[1].Type; + } else { + return SpecialType.UnknownType; + } + } + } + internal ILInstruction MakeLifted(ComparisonKind newComparisonKind, ILInstruction left, ILInstruction right) { if (Instruction is Comp comp) { @@ -420,7 +440,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// ILInstruction LiftCSharpComparison(CompOrDecimal comp, ComparisonKind newComparisonKind) { - var (left, right, bits) = DoLiftBinary(comp.Left, comp.Right); + var (left, right, bits) = DoLiftBinary(comp.Left, comp.Right, comp.LeftExpectedType, comp.RightExpectedType); // 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)) { @@ -541,7 +561,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (liftedOperator != null) { context.Step("Lift user-defined comparison operator", trueInst); - var (left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1]); + var (left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1], + call.Method.Parameters[0].Type, call.Method.Parameters[1].Type); if (left != null && right != null && bits.All(0, nullableVars.Count)) { return new Call(liftedOperator) { Arguments = { left, right }, @@ -661,7 +682,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (newInst, bits); } } else if (inst is BinaryNumericInstruction binary) { - var (left, right, bits) = DoLiftBinary(binary.Left, binary.Right); + var (left, right, bits) = DoLiftBinary(binary.Left, binary.Right, SpecialType.UnknownType, SpecialType.UnknownType); if (left != null && right != null) { if (binary.HasDirectFlag(InstructionFlags.MayThrow) && !bits.All(0, nullableVars.Count)) { // Cannot execute potentially-throwing instruction unless all @@ -704,7 +725,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms newArgs = new[] { arg }; newBits = bits; } else if (call.Arguments.Count == 2) { - var (left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1]); + var (left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1], + call.Method.Parameters[0].Type, call.Method.Parameters[1].Type); newArgs = new[] { left, right }; newBits = bits; } else { @@ -726,17 +748,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (null, null); } - (ILInstruction, ILInstruction, BitSet) DoLiftBinary(ILInstruction lhs, ILInstruction rhs) + (ILInstruction, ILInstruction, BitSet) DoLiftBinary(ILInstruction lhs, ILInstruction rhs, IType leftExpectedType, IType rightExpectedType) { var (left, leftBits) = DoLift(lhs); var (right, rightBits) = DoLift(rhs); if (left != null && right == null && SemanticHelper.IsPure(rhs.Flags)) { // Embed non-nullable pure expression in lifted expression. - right = rhs.Clone(); + right = NewNullable(rhs.Clone(), rightExpectedType); } if (left == null && right != null && SemanticHelper.IsPure(lhs.Flags)) { // Embed non-nullable pure expression in lifted expression. - left = lhs.Clone(); + left = NewNullable(lhs.Clone(), leftExpectedType); } if (left != null && right != null) { var bits = leftBits ?? rightBits; @@ -747,6 +769,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms return (null, null, null); } } + + private ILInstruction NewNullable(ILInstruction inst, IType underlyingType) + { + if (underlyingType == SpecialType.UnknownType) + return inst; + var nullable = context.TypeSystem.Compilation.FindType(KnownTypeCode.NullableOfT).GetDefinition(); + var ctor = nullable?.Methods.FirstOrDefault(m => m.IsConstructor && m.Parameters.Count == 1); + if (ctor != null) { + ctor = ctor.Specialize(new TypeParameterSubstitution(new[] { underlyingType }, null)); + return new NewObj(ctor) { Arguments = { inst } }; + } else { + return inst; + } + } #endregion #region Match...Call @@ -799,7 +835,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// Matches 'newobj Nullable{underlyingType}.ctor(arg)' /// - static bool MatchNullableCtor(ILInstruction inst, out IType underlyingType, out ILInstruction arg) + internal static bool MatchNullableCtor(ILInstruction inst, out IType underlyingType, out ILInstruction arg) { underlyingType = null; arg = null;