diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index f5d4d50..bb5684a 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -932,6 +932,12 @@ namespace ICSharpCode.Decompiler.Tests await RunForLibrary(cscOptions: cscOptions); } + [Test] + public async Task Issue4153([ValueSource(nameof(roslynOnlyWithNet40Options))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + [Test] public async Task AssemblyCustomAttributes([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue4153.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue4153.cs new file mode 100644 index 0000000..85319ca --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Issue4153.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + internal class Issue4153 + { + private static int G(int i) + { + return i; + } + + private static int M(int x, int y) + { + return x + y; + } + + private int N(int x, int y) + { + return x - y; + } + + public static int DictionaryEntries(Dictionary d) + { + int num = 0; + foreach (KeyValuePair item in d) + { + num += M(y: G(item.Key), x: G(2)); + } + return num; + } + + public static int ListOfPairs(List> list) + { + int num = 0; + foreach (KeyValuePair item in list) + { + num += M(y: G(item.Key), x: G(2)); + } + return num; + } + + public int InstanceCall(Dictionary d) + { + int num = 0; + foreach (KeyValuePair item in d) + { + num += N(y: G(item.Key), x: G(2)); + } + return num; + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs index af03ca0..02e46d3 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs @@ -271,6 +271,10 @@ namespace ICSharpCode.Decompiler.IL case BlockKind.CallInlineAssign: // Allow inlining into the first instruction of the block return childIndex == 0; + case BlockKind.CallWithNamedArgs: + // Same, but the first named argument of an instance call comes after its 'this' argument, + // see NamedArgumentTransform.CanExtendNamedArgument. + return childIndex == 0 || (childIndex == 1 && FinalInstruction is CallInstruction { IsInstanceCall: true }); default: return false; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 6c0c98c..2833ebc 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -942,6 +942,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms for (int i = 0; i < childIndex; ++i) { ILInstruction predecessor = inst.Parent.Children[i]; + // The variable of a named argument only exists within its block, + // so only the stored value can interfere with the moved expression. + if (predecessor is StLoc { Variable.Kind: VariableKind.NamedArgument } namedArgument) + predecessor = namedArgument.Value; if (!IsSafeForInlineOver(predecessor, expressionBeingMoved)) return false; }