From d88c73f59df9360636dc1b37ce98ee9713e135a0 Mon Sep 17 00:00:00 2001 From: SilverFox Date: Thu, 14 Nov 2019 22:33:30 +0800 Subject: [PATCH 01/15] Add test for generic local function --- .../TestCases/Pretty/LocalFunctions.cs | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 91c58bacd..2e1442936 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -23,6 +23,113 @@ namespace LocalFunctions { internal class LocalFunctions { + public class Generic where T1 : struct, ICloneable, IConvertible + { + public int MixedLocalFunction() where T2 : ICloneable, IConvertible + { + object z = this; + for (int j = 0; j < 10; j++) { + int i = 0; + i += NonStaticMethod6(); + int NonStaticMethod6() + { + int l = 0; + return NonStaticMethod6_1() + NonStaticMethod6_1() + z.GetHashCode(); + int NonStaticMethod6_1() + { + return i + l + NonStaticMethod6() + StaticMethod1(); + } + } + } + return MixedLocalFunction() + MixedLocalFunction() + StaticMethod1() + StaticMethod1() + NonStaticMethod3() + StaticMethod4(null) + StaticMethod5(); + int NonStaticMethod3() + { + return GetHashCode(); + } + int StaticMethod1() where T3 : struct + { + return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticMethod1() + StaticMethod1_1() + StaticMethod2(); + } + int StaticMethod1_1() where T3 : struct where T4 : Enum + { + return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + typeof(T4).Name.Length + StaticMethod1() + StaticMethod1_1(); + } + int StaticMethod2() where T2 : IConvertible where T3 : struct where T4 : Enum + { + return typeof(T2).Name.Length; + } + int StaticMethod4(T dd) + { + return 0; + } + int StaticMethod5() + { + int k = 0; + return k + NonStaticMethod5_1(); + int NonStaticMethod5_1() + { + return k; + } + } + } + + public int MixedLocalFunction2Delegate() where T2 : ICloneable, IConvertible + { + object z = this; + for (int j = 0; j < 10; j++) { + int i = 0; + i += StaticInvokeAsFunc(NonStaticMethod6); + int NonStaticMethod6() + { + int l = 0; + return StaticInvokeAsFunc(NonStaticMethod6_1) + StaticInvokeAsFunc(NonStaticMethod6_1) + z.GetHashCode(); + int NonStaticMethod6_1() + { + return i + l + StaticInvokeAsFunc(NonStaticMethod6) + StaticInvokeAsFunc(StaticMethod1); + } + } + } + return StaticInvokeAsFunc(MixedLocalFunction2Delegate) + StaticInvokeAsFunc(MixedLocalFunction2Delegate) + StaticInvokeAsFunc(StaticMethod1) + StaticInvokeAsFunc(StaticMethod1) + StaticInvokeAsFunc(NonStaticMethod3) + StaticInvokeAsFunc(StaticMethod5) + new Func(StaticMethod4)(null) + StaticInvokeAsFunc2(StaticMethod4) + new Func, int>(StaticInvokeAsFunc2)(StaticMethod4); + int NonStaticMethod3() + { + return GetHashCode(); + } + int StaticInvokeAsFunc(Func func) + { + return func(); + } + int StaticInvokeAsFunc2(Func func) + { + return func(default(T)); + } + int StaticMethod1() where T3 : struct + { + return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticInvokeAsFunc(StaticMethod1) + StaticInvokeAsFunc(StaticMethod1_1) + StaticInvokeAsFunc(StaticMethod2); + } + int StaticMethod1_1() where T3 : struct where T4 : Enum + { + return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + typeof(T4).Name.Length + StaticInvokeAsFunc(StaticMethod1) + StaticInvokeAsFunc(StaticMethod1_1); + } + int StaticMethod2() where T2 : IConvertible where T3 : struct where T4 : Enum + { + return typeof(T2).Name.Length; + } + int StaticMethod4(T dd) + { + return 0; + } + int StaticMethod5() + { + int k = 0; + return k + StaticInvokeAsFunc(NonStaticMethod5_1); + int NonStaticMethod5_1() + { + return k; + } + } + } + } + private int field; private Lazy nonCapturinglocalFunctionInLambda = new Lazy(delegate { From f039705704bc4b971c28c3949f1bcafb4e4b35c7 Mon Sep 17 00:00:00 2001 From: SilverFox Date: Thu, 14 Nov 2019 23:11:20 +0800 Subject: [PATCH 02/15] Add support for generic local function --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 45 ++++++++++++++++--- .../CSharp/ExpressionBuilder.cs | 4 +- .../CSharp/StatementBuilder.cs | 11 +++++ .../CSharp/Syntax/TypeSystemAstBuilder.cs | 4 +- ICSharpCode.Decompiler/IL/ILReader.cs | 1 - .../IL/Transforms/LocalFunctionDecompiler.cs | 15 ++++--- 6 files changed, 62 insertions(+), 18 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 3a79c3504..ea43abd63 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -195,8 +195,21 @@ namespace ICSharpCode.Decompiler.CSharp if (callOpCode == OpCode.NewObj) { target = default(TranslatedExpression); // no target } else if (method.IsLocalFunction && localFunction != null) { - target = new IdentifierExpression(localFunction.Name) - .WithoutILInstruction() + var ide = new IdentifierExpression(localFunction.Name); + if (method.TypeArguments.Count > 0) { + var parentMethod = ((ILFunction)localFunction.Parent).Method; + int skipCount = parentMethod.DeclaringType.TypeParameterCount + parentMethod.TypeParameters.Count - localFunction.Method.DeclaringType.TypeParameterCount; +#if DEBUG + Debug.Assert(skipCount >= 0); + if (skipCount > 0) { + var currentMethod = expressionBuilder.currentFunction.Method; + currentMethod = currentMethod.ReducedFrom ?? currentMethod; + Debug.Assert(currentMethod.DeclaringType.TypeParameters.Concat(currentMethod.TypeParameters).Take(skipCount).SequenceEqual(method.DeclaringType.TypeArguments.Concat(method.TypeArguments).Take(skipCount))); + } +#endif + ide.TypeArguments.AddRange(method.TypeArguments.Skip(skipCount).Select(expressionBuilder.ConvertType)); + } + target = ide.WithoutILInstruction() .WithRR(ToMethodGroup(method, localFunction)); } else { target = expressionBuilder.TranslateTarget( @@ -1304,10 +1317,14 @@ namespace ICSharpCode.Decompiler.CSharp // 2. add type arguments (represented as bit 1) // 3. cast target (represented as bit 2) int step; + ILFunction localFunction = null; if (method.IsLocalFunction) { - step = 0; + localFunction = expressionBuilder.ResolveLocalFunction(method); + Debug.Assert(localFunction != null); + } + if (localFunction != null) { + step = 2; requireTarget = false; - var localFunction = expressionBuilder.ResolveLocalFunction(method); result = ToMethodGroup(method, localFunction); target = default; targetType = default; @@ -1389,8 +1406,22 @@ namespace ICSharpCode.Decompiler.CSharp targetExpression = mre.WithRR(result); } else { var ide = new IdentifierExpression(methodName); - if ((step & 2) != 0) - ide.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType)); + if ((step & 2) != 0) { + int skipCount = 0; + if (localFunction != null && method.TypeArguments.Count > 0) { + var parentMethod = ((ILFunction)localFunction.Parent).Method; + skipCount = parentMethod.DeclaringType.TypeParameterCount + parentMethod.TypeParameters.Count - localFunction.Method.DeclaringType.TypeParameterCount; +#if DEBUG + Debug.Assert(skipCount >= 0); + if (skipCount > 0) { + var currentMethod = expressionBuilder.currentFunction.Method; + currentMethod = currentMethod.ReducedFrom ?? currentMethod; + Debug.Assert(currentMethod.DeclaringType.TypeParameters.Concat(currentMethod.TypeParameters).Take(skipCount).SequenceEqual(method.DeclaringType.TypeArguments.Concat(method.TypeArguments).Take(skipCount))); + } +#endif + } + ide.TypeArguments.AddRange(method.TypeArguments.Skip(skipCount).Select(expressionBuilder.ConvertType)); + } targetExpression = ide.WithRR(result); } return targetExpression; @@ -1448,7 +1479,7 @@ namespace ICSharpCode.Decompiler.CSharp method.DeclaringType, new IParameterizedMember[] { method } ) - }, EmptyList.Instance + }, method.TypeArguments ); } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 0c9c6f28f..d6f012783 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -73,7 +73,7 @@ namespace ICSharpCode.Decompiler.CSharp internal readonly ILFunction currentFunction; internal readonly ICompilation compilation; internal readonly CSharpResolver resolver; - readonly TypeSystemAstBuilder astBuilder; + internal readonly TypeSystemAstBuilder astBuilder; internal readonly TypeInference typeInference; internal readonly DecompilerSettings settings; readonly CancellationToken cancellationToken; @@ -217,7 +217,7 @@ namespace ICSharpCode.Decompiler.CSharp internal ILFunction ResolveLocalFunction(IMethod method) { Debug.Assert(method.IsLocalFunction); - method = method.ReducedFrom; + method = (IMethod)method.ReducedFrom.MemberDefinition; foreach (var parent in currentFunction.Ancestors.OfType()) { var definition = parent.LocalFunctions.FirstOrDefault(f => f.Method.Equals(method)); if (definition != null) { diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 432f706ad..4a6389adb 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -987,6 +987,17 @@ namespace ICSharpCode.Decompiler.CSharp stmt.Parameters.AddRange(exprBuilder.MakeParameters(function.Parameters, function)); stmt.ReturnType = exprBuilder.ConvertType(function.Method.ReturnType); stmt.Body = nestedBuilder.ConvertAsBlock(function.Body); + if (function.Method.TypeParameters.Count > 0) { + var parentMethod = ((ILFunction)function.Parent).Method; + int skipCount = parentMethod.DeclaringType.TypeParameterCount + parentMethod.TypeParameters.Count - function.Method.DeclaringType.TypeParameterCount; + var astBuilder = exprBuilder.astBuilder; + if (astBuilder.ShowTypeParameters) { + stmt.TypeParameters.AddRange(function.Method.TypeParameters.Skip(skipCount).Select(t => astBuilder.ConvertTypeParameter(t))); + if (astBuilder.ShowTypeParameterConstraints) { + stmt.Constraints.AddRange(function.Method.TypeParameters.Skip(skipCount).Select(t => astBuilder.ConvertTypeParameterConstraint(t)).Where(c => c != null)); + } + } + } if (function.IsAsync) { stmt.Modifiers |= Modifiers.Async; } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index a4a77358d..d665f41fe 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -1793,7 +1793,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax #endregion #region Convert Type Parameter - TypeParameterDeclaration ConvertTypeParameter(ITypeParameter tp) + internal TypeParameterDeclaration ConvertTypeParameter(ITypeParameter tp) { TypeParameterDeclaration decl = new TypeParameterDeclaration(); decl.Variance = tp.Variance; @@ -1803,7 +1803,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return decl; } - Constraint ConvertTypeParameterConstraint(ITypeParameter tp) + internal Constraint ConvertTypeParameterConstraint(ITypeParameter tp) { if (!tp.HasDefaultConstructorConstraint && !tp.HasReferenceTypeConstraint && !tp.HasValueTypeConstraint && tp.NullabilityConstraint != Nullability.NotNullable && tp.DirectBaseTypes.All(IsObjectOrValueType)) { return null; diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index 2bea3eb93..0fdb11d5d 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -100,7 +100,6 @@ namespace ICSharpCode.Decompiler.IL this.method = this.method.Specialize(genericContext.ToSubstitution()); } this.genericContext = genericContext; - var methodDefinition = metadata.GetMethodDefinition(methodDefinitionHandle); this.body = body; this.reader = body.GetILReader(); this.currentStack = ImmutableStack.Empty; diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index 5adbdf4e0..f2af0b636 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -91,10 +91,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms foreach (var useSite in info.UseSites) { context.Step($"Transform use site at IL_{useSite.StartILOffset:x4}", useSite); + DetermineCaptureAndDeclarationScope(localFunction, useSite); if (useSite.OpCode == OpCode.NewObj) { TransformToLocalFunctionReference(localFunction, useSite); } else { - DetermineCaptureAndDeclarationScope(localFunction, useSite); TransformToLocalFunctionInvocation(localFunction.ReducedMethod, useSite); } @@ -129,6 +129,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms void HandleUseSite(IMethod targetMethod, CallInstruction inst) { if (!localFunctions.TryGetValue((MethodDefinitionHandle)targetMethod.MetadataToken, out var info)) { + targetMethod = (IMethod)targetMethod.MemberDefinition; context.StepStartGroup($"Read local function '{targetMethod.Name}'", inst); info = new LocalFunctionInfo() { UseSites = new List() { inst }, @@ -151,12 +152,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken); if (!methodDefinition.HasBody()) return null; - var genericContext = DelegateConstruction.GenericContextFromTypeArguments(targetMethod.Substitution); - if (genericContext == null) - return null; var ilReader = context.CreateILReader(); var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); - var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.LocalFunction, context.CancellationToken); + var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, default, ILFunctionKind.LocalFunction, context.CancellationToken); // Embed the local function into the parent function's ILAst, so that "Show steps" can show // how the local function body is being transformed. rootFunction.LocalFunctions.Add(function); @@ -219,12 +217,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms { useSite.Arguments[0].ReplaceWith(new LdNull().WithILRange(useSite.Arguments[0])); var fnptr = (IInstructionWithMethodOperand)useSite.Arguments[1]; - var replacement = new LdFtn(function.ReducedMethod).WithILRange((ILInstruction)fnptr); + LocalFunctionMethod reducedMethod = function.ReducedMethod; + if (reducedMethod.TypeParameters.Count > 0) + reducedMethod = new LocalFunctionMethod(fnptr.Method, reducedMethod.NumberOfCompilerGeneratedParameters); + var replacement = new LdFtn(reducedMethod).WithILRange((ILInstruction)fnptr); useSite.Arguments[1].ReplaceWith(replacement); } void TransformToLocalFunctionInvocation(LocalFunctionMethod reducedMethod, CallInstruction useSite) { + if (reducedMethod.TypeParameters.Count > 0) + reducedMethod = new LocalFunctionMethod(useSite.Method, reducedMethod.NumberOfCompilerGeneratedParameters); bool wasInstanceCall = !useSite.Method.IsStatic; var replacement = new Call(reducedMethod); int firstArgumentIndex = wasInstanceCall ? 1 : 0; From 6409fe945ce81061fc9ac0cd1f54167cdd7fb004 Mon Sep 17 00:00:00 2001 From: SilverFox Date: Thu, 14 Nov 2019 23:14:11 +0800 Subject: [PATCH 03/15] Add support for static local function --- .../CSharp/StatementBuilder.cs | 3 +++ ICSharpCode.Decompiler/DecompilerSettings.cs | 20 ++++++++++++++++++- ILSpy/Properties/Resources.Designer.cs | 9 +++++++++ ILSpy/Properties/Resources.resx | 3 +++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 4a6389adb..ea0076215 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -1001,6 +1001,9 @@ namespace ICSharpCode.Decompiler.CSharp if (function.IsAsync) { stmt.Modifiers |= Modifiers.Async; } + if (settings.StaticLocalFunctions && function.Method.IsStatic && function.ReducedMethod.NumberOfCompilerGeneratedParameters == 0) { + stmt.Modifiers |= Modifiers.Static; + } stmt.AddAnnotation(new MemberResolveResult(null, function.ReducedMethod)); return stmt; } diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index fed45dec5..1cf0ce8e0 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -110,12 +110,13 @@ namespace ICSharpCode.Decompiler readOnlyMethods = false; asyncUsingAndForEachStatement = false; asyncEnumerator = false; + staticLocalFunctions = false; } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement) + if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions) return CSharp.LanguageVersion.CSharp8_0; if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers) return CSharp.LanguageVersion.CSharp7_3; @@ -1064,6 +1065,23 @@ namespace ICSharpCode.Decompiler } } + bool staticLocalFunctions = true; + + /// + /// Gets/Sets whether C# 8.0 static local functions should be transformed. + /// + [Category("C# 8.0 / VS 2019")] + [Description("DecompilerSettings.IntroduceStaticLocalFunctions")] + public bool StaticLocalFunctions { + get { return staticLocalFunctions; } + set { + if (staticLocalFunctions != value) { + staticLocalFunctions = value; + OnPropertyChanged(); + } + } + } + bool nullableReferenceTypes = true; /// diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index dd8e2a829..3a328e430 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -772,6 +772,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Introduce static local functions. + /// + public static string DecompilerSettings_IntroduceStaticLocalFunctions { + get { + return ResourceManager.GetString("DecompilerSettings.IntroduceStaticLocalFunctions", resourceCulture); + } + } + /// /// Looks up a localized string similar to IsByRefLikeAttribute should be replaced with 'ref' modifiers on structs. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 7aa7db1df..7e75bea99 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -775,4 +775,7 @@ Are you sure you want to continue? Detect awaited using and foreach statements + + Introduce static local functions + \ No newline at end of file From 74dec0f7fb7717e9ce5d54046e0e6be13bf563e2 Mon Sep 17 00:00:00 2001 From: SilverFox Date: Thu, 14 Nov 2019 23:15:35 +0800 Subject: [PATCH 04/15] Disable static local functions until tests is fixed --- ICSharpCode.Decompiler/DecompilerSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 1cf0ce8e0..94825477a 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -1065,7 +1065,7 @@ namespace ICSharpCode.Decompiler } } - bool staticLocalFunctions = true; + bool staticLocalFunctions = false; /// /// Gets/Sets whether C# 8.0 static local functions should be transformed. From b8f00cf983c283a9ebdefea616b395c59be7f60d Mon Sep 17 00:00:00 2001 From: SilverFox Date: Thu, 14 Nov 2019 23:17:51 +0800 Subject: [PATCH 05/15] Add some new tests for local func --- .../TestCases/Pretty/LocalFunctions.cs | 110 +++++++++++++++++- 1 file changed, 106 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 2e1442936..17c7ae6e1 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -27,12 +27,14 @@ namespace LocalFunctions { public int MixedLocalFunction() where T2 : ICloneable, IConvertible { + T2 t2 = default(T2); object z = this; for (int j = 0; j < 10; j++) { int i = 0; i += NonStaticMethod6(); int NonStaticMethod6() { + t2 = default(T2); int l = 0; return NonStaticMethod6_1() + NonStaticMethod6_1() + z.GetHashCode(); int NonStaticMethod6_1() @@ -48,13 +50,15 @@ namespace LocalFunctions } int StaticMethod1() where T3 : struct { - return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticMethod1() + StaticMethod1_1() + StaticMethod2(); + return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticMethod1() + StaticMethod1_1() + StaticMethod2_RepeatT2(); } int StaticMethod1_1() where T3 : struct where T4 : Enum { return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + typeof(T4).Name.Length + StaticMethod1() + StaticMethod1_1(); } - int StaticMethod2() where T2 : IConvertible where T3 : struct where T4 : Enum +#pragma warning disable CS8387 + int StaticMethod2_RepeatT2() where T2 : IConvertible where T3 : struct where T4 : Enum +#pragma warning restore CS8387 { return typeof(T2).Name.Length; } @@ -75,12 +79,14 @@ namespace LocalFunctions public int MixedLocalFunction2Delegate() where T2 : ICloneable, IConvertible { + T2 t2 = default(T2); object z = this; for (int j = 0; j < 10; j++) { int i = 0; i += StaticInvokeAsFunc(NonStaticMethod6); int NonStaticMethod6() { + t2 = default(T2); int l = 0; return StaticInvokeAsFunc(NonStaticMethod6_1) + StaticInvokeAsFunc(NonStaticMethod6_1) + z.GetHashCode(); int NonStaticMethod6_1() @@ -104,13 +110,15 @@ namespace LocalFunctions } int StaticMethod1() where T3 : struct { - return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticInvokeAsFunc(StaticMethod1) + StaticInvokeAsFunc(StaticMethod1_1) + StaticInvokeAsFunc(StaticMethod2); + return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticInvokeAsFunc(StaticMethod1) + StaticInvokeAsFunc(StaticMethod1_1) + StaticInvokeAsFunc(StaticMethod2_RepeatT2); } int StaticMethod1_1() where T3 : struct where T4 : Enum { return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + typeof(T4).Name.Length + StaticInvokeAsFunc(StaticMethod1) + StaticInvokeAsFunc(StaticMethod1_1); } - int StaticMethod2() where T2 : IConvertible where T3 : struct where T4 : Enum +#pragma warning disable CS8387 + int StaticMethod2_RepeatT2() where T2 : IConvertible where T3 : struct where T4 : Enum +#pragma warning restore CS8387 { return typeof(T2).Name.Length; } @@ -128,6 +136,52 @@ namespace LocalFunctions } } } + + public static void Test_CaptureT() + { + T2 t2 = default(T2); + Method1(); + void Method1() + { + t2 = default(T2); + T2 t2x = t2; + T3 t3 = default(T3); + Method1_1(); + void Method1_1() + { + t2 = default(T2); + t2x = t2; + t3 = default(T3); + } + } + } + + public void TestGenericArgs() where T2 : List + { + ZZ(null); + ZZ2(null); + void Nop(T data) + { + } + void ZZ(T3 t3) where T3 : T2 + { + Nop>(t3); + ZZ1(t3); + ZZ3(); + void ZZ3() + { + Nop>(t3); + } + } + void ZZ1(T3 t3) + { + Nop>((List)(object)t3); + } + void ZZ2(T3 t3) + { + Nop>((List)(object)t3); + } + } } private int field; @@ -439,5 +493,53 @@ namespace LocalFunctions // } // } //} + + public void NestedCapture1() + { + Method1(null); + + Action Method1(Action action) + { + return Method1_1; + + void Method1_1(object containerBuilder) => + Method1_2(containerBuilder); + + void Method1_2(object containerBuilder) => + action(containerBuilder); + } + } + + public int NestedCapture2() + { + return Method(); + int Method() + { + int t0 = 0; + return ZZZ_0(); + int ZZZ_0() + { + t0 = 0; + var t1 = t0; + Func zzz2 = () => { + t0 = 0; + t1 = 0; + return ZZZ_1(); + }; + return zzz2(); + } + int ZZZ_1() + { + t0 = 0; + var t1 = t0; + Func zzz = () => { + t0 = 0; + t1 = 0; + return 0; + }; + return zzz(); + } + } + } } } From e60f1f5a87b036a4938a0c4180bc8cf8e93ee34a Mon Sep 17 00:00:00 2001 From: SilverFox Date: Thu, 14 Nov 2019 23:19:32 +0800 Subject: [PATCH 06/15] Skip LocalFunctionDecompiler if the top scope is LocalFunction or LocalFunctionDisplayClass --- ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index f2af0b636..ee62f568b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -65,6 +65,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms { if (!context.Settings.LocalFunctions) return; + if (IsLocalFunctionMethod(function.Method, context) || IsLocalFunctionDisplayClass(function.Method.ParentModule.PEFile, (TypeDefinitionHandle)function.Method.DeclaringTypeDefinition.MetadataToken, context)) + return; this.context = context; this.resolveContext = new SimpleTypeResolveContext(function.Method); var localFunctions = new Dictionary(); From 5e6fecebf5046a01b62824886768d6357c7a6b5b Mon Sep 17 00:00:00 2001 From: SilverFox Date: Thu, 14 Nov 2019 23:28:24 +0800 Subject: [PATCH 07/15] Rework support for generic local function, and fix tests `LocalFunctions.Generic.Test_CaptureT` and `LocalFunctions.Generic.TestGenericArgs` --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 35 ++--- .../CSharp/ExpressionBuilder.cs | 4 +- .../CSharp/StatementBuilder.cs | 5 +- .../IL/Transforms/LocalFunctionDecompiler.cs | 147 +++++++++++++++--- .../Implementation/LocalFunctionMethod.cs | 4 + 5 files changed, 143 insertions(+), 52 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index ea43abd63..0d6fde5da 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -194,19 +194,10 @@ namespace ICSharpCode.Decompiler.CSharp TranslatedExpression target; if (callOpCode == OpCode.NewObj) { target = default(TranslatedExpression); // no target - } else if (method.IsLocalFunction && localFunction != null) { + } else if (localFunction != null) { var ide = new IdentifierExpression(localFunction.Name); if (method.TypeArguments.Count > 0) { - var parentMethod = ((ILFunction)localFunction.Parent).Method; - int skipCount = parentMethod.DeclaringType.TypeParameterCount + parentMethod.TypeParameters.Count - localFunction.Method.DeclaringType.TypeParameterCount; -#if DEBUG - Debug.Assert(skipCount >= 0); - if (skipCount > 0) { - var currentMethod = expressionBuilder.currentFunction.Method; - currentMethod = currentMethod.ReducedFrom ?? currentMethod; - Debug.Assert(currentMethod.DeclaringType.TypeParameters.Concat(currentMethod.TypeParameters).Take(skipCount).SequenceEqual(method.DeclaringType.TypeArguments.Concat(method.TypeArguments).Take(skipCount))); - } -#endif + int skipCount = localFunction.ReducedMethod.NumberOfCompilerGeneratedGenerics; ide.TypeArguments.AddRange(method.TypeArguments.Skip(skipCount).Select(expressionBuilder.ConvertType)); } target = ide.WithoutILInstruction() @@ -238,7 +229,7 @@ namespace ICSharpCode.Decompiler.CSharp var argumentList = BuildArgumentList(expectedTargetDetails, target.ResolveResult, method, firstParamIndex, callArguments, argumentToParameterMap); - if (method.IsLocalFunction) { + if (localFunction != null) { return new InvocationExpression(target, argumentList.GetArgumentExpressions()) .WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method, argumentList.GetArgumentResolveResults().ToList(), isExpandedForm: argumentList.IsExpandedForm)); @@ -1368,11 +1359,12 @@ namespace ICSharpCode.Decompiler.CSharp memberDeclaringType: method.DeclaringType); requireTarget = expressionBuilder.HidesVariableWithName(method.Name) || (method.IsStatic ? !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition) : !(target.Expression is ThisReferenceExpression)); - + step = requireTarget ? 1 : 0; var savedTarget = target; - for (step = requireTarget ? 1 : 0; step < 7; step++) { + for (; step < 7; step++) { ResolveResult targetResolveResult; - if (!method.IsLocalFunction && (step & 1) != 0) { + //TODO: why there is an check for IsLocalFunction here, it should be unreachable in old code + if (localFunction == null && (step & 1) != 0) { targetResolveResult = savedTarget.ResolveResult; target = savedTarget; } else { @@ -1395,7 +1387,7 @@ namespace ICSharpCode.Decompiler.CSharp break; } } - requireTarget = !method.IsLocalFunction && (step & 1) != 0; + requireTarget = localFunction == null && (step & 1) != 0; ExpressionWithResolveResult targetExpression; Debug.Assert(result != null); if (requireTarget) { @@ -1409,16 +1401,7 @@ namespace ICSharpCode.Decompiler.CSharp if ((step & 2) != 0) { int skipCount = 0; if (localFunction != null && method.TypeArguments.Count > 0) { - var parentMethod = ((ILFunction)localFunction.Parent).Method; - skipCount = parentMethod.DeclaringType.TypeParameterCount + parentMethod.TypeParameters.Count - localFunction.Method.DeclaringType.TypeParameterCount; -#if DEBUG - Debug.Assert(skipCount >= 0); - if (skipCount > 0) { - var currentMethod = expressionBuilder.currentFunction.Method; - currentMethod = currentMethod.ReducedFrom ?? currentMethod; - Debug.Assert(currentMethod.DeclaringType.TypeParameters.Concat(currentMethod.TypeParameters).Take(skipCount).SequenceEqual(method.DeclaringType.TypeArguments.Concat(method.TypeArguments).Take(skipCount))); - } -#endif + skipCount = localFunction.ReducedMethod.NumberOfCompilerGeneratedGenerics; } ide.TypeArguments.AddRange(method.TypeArguments.Skip(skipCount).Select(expressionBuilder.ConvertType)); } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index d6f012783..cee0b3531 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -217,9 +217,9 @@ namespace ICSharpCode.Decompiler.CSharp internal ILFunction ResolveLocalFunction(IMethod method) { Debug.Assert(method.IsLocalFunction); - method = (IMethod)method.ReducedFrom.MemberDefinition; + method = (IMethod)((IMethod)method.MemberDefinition).ReducedFrom.MemberDefinition; foreach (var parent in currentFunction.Ancestors.OfType()) { - var definition = parent.LocalFunctions.FirstOrDefault(f => f.Method.Equals(method)); + var definition = parent.LocalFunctions.FirstOrDefault(f => f.Method.MemberDefinition.Equals(method)); if (definition != null) { return definition; } diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index ea0076215..1c58f6e92 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -988,10 +988,9 @@ namespace ICSharpCode.Decompiler.CSharp stmt.ReturnType = exprBuilder.ConvertType(function.Method.ReturnType); stmt.Body = nestedBuilder.ConvertAsBlock(function.Body); if (function.Method.TypeParameters.Count > 0) { - var parentMethod = ((ILFunction)function.Parent).Method; - int skipCount = parentMethod.DeclaringType.TypeParameterCount + parentMethod.TypeParameters.Count - function.Method.DeclaringType.TypeParameterCount; var astBuilder = exprBuilder.astBuilder; if (astBuilder.ShowTypeParameters) { + int skipCount = function.ReducedMethod.NumberOfCompilerGeneratedGenerics; stmt.TypeParameters.AddRange(function.Method.TypeParameters.Skip(skipCount).Select(t => astBuilder.ConvertTypeParameter(t))); if (astBuilder.ShowTypeParameterConstraints) { stmt.Constraints.AddRange(function.Method.TypeParameters.Skip(skipCount).Select(t => astBuilder.ConvertTypeParameterConstraint(t)).Where(c => c != null)); @@ -1001,7 +1000,7 @@ namespace ICSharpCode.Decompiler.CSharp if (function.IsAsync) { stmt.Modifiers |= Modifiers.Async; } - if (settings.StaticLocalFunctions && function.Method.IsStatic && function.ReducedMethod.NumberOfCompilerGeneratedParameters == 0) { + if (settings.StaticLocalFunctions && function.ReducedMethod.IsStaticLocalFunction) { stmt.Modifiers |= Modifiers.Static; } stmt.AddAnnotation(new MemberResolveResult(null, function.ReducedMethod)); diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index ee62f568b..f71ccf4ce 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -80,25 +80,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms continue; } - var firstUseSite = info.UseSites[0]; context.StepStartGroup($"Transform " + info.Definition.Name, info.Definition); try { var localFunction = info.Definition; if (!localFunction.Method.IsStatic) { - var target = firstUseSite.Arguments[0]; + var target = info.UseSites[0].Arguments[0]; context.Step($"Replace 'this' with {target}", localFunction); var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); } foreach (var useSite in info.UseSites) { - context.Step($"Transform use site at IL_{useSite.StartILOffset:x4}", useSite); DetermineCaptureAndDeclarationScope(localFunction, useSite); - if (useSite.OpCode == OpCode.NewObj) { - TransformToLocalFunctionReference(localFunction, useSite); - } else { - TransformToLocalFunctionInvocation(localFunction.ReducedMethod, useSite); - } if (function.Method.IsConstructor && localFunction.DeclarationScope == null) { localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite); @@ -111,12 +104,69 @@ namespace ICSharpCode.Decompiler.IL.Transforms function.LocalFunctions.Remove(localFunction); declaringFunction.LocalFunctions.Add(localFunction); } + + if (TryValidateSkipCount(info, out int skipCount) && skipCount != localFunction.ReducedMethod.NumberOfCompilerGeneratedGenerics) { + Debug.Assert(false); + function.Warnings.Add($"Could not decode local function '{info.Method}'"); + if (localFunction.DeclarationScope != function.Body && localFunction.DeclarationScope.Parent is ILFunction declaringFunction) { + declaringFunction.LocalFunctions.Remove(localFunction); + } + continue; + } + + foreach (var useSite in info.UseSites) { + context.Step($"Transform use site at IL_{useSite.StartILOffset:x4}", useSite); + if (useSite.OpCode == OpCode.NewObj) { + TransformToLocalFunctionReference(localFunction, useSite); + } else { + TransformToLocalFunctionInvocation(localFunction.ReducedMethod, useSite); + } + } } finally { context.StepEndGroup(); } } } + bool TryValidateSkipCount(LocalFunctionInfo info, out int skipCount) + { + skipCount = 0; + var localFunction = info.Definition; + if (localFunction.Method.TypeParameters.Count == 0) + return true; + var parentMethod = ((ILFunction)localFunction.Parent).Method; + var method = localFunction.Method; + skipCount = parentMethod.DeclaringType.TypeParameterCount - method.DeclaringType.TypeParameterCount; + + if (skipCount > 0) + return false; + skipCount += parentMethod.TypeParameters.Count; + Debug.Assert(skipCount >= 0 && skipCount <= method.TypeArguments.Count); + if (skipCount < 0 || skipCount > method.TypeArguments.Count) + return false; + + if (skipCount > 0) { +#if DEBUG + foreach (var useSite in info.UseSites) { + var callerMethod = useSite.Ancestors.OfType().First().Method; + callerMethod = callerMethod.ReducedFrom ?? callerMethod; + IMethod method0; + if (useSite.OpCode == OpCode.NewObj) { + method0 = ((LdFtn)useSite.Arguments[1]).Method; + } else { + method0 = useSite.Method; + } + var totalSkipCount = skipCount + method0.DeclaringType.TypeParameterCount; + var methodSkippedArgs = method0.DeclaringType.TypeArguments.Concat(method0.TypeArguments).Take(totalSkipCount); + Debug.Assert(methodSkippedArgs.SequenceEqual(callerMethod.DeclaringType.TypeArguments.Concat(callerMethod.TypeArguments).Take(totalSkipCount))); + Debug.Assert(methodSkippedArgs.All(p => p.Kind == TypeKind.TypeParameter)); + Debug.Assert(methodSkippedArgs.Select(p => p.Name).SequenceEqual(method0.DeclaringType.TypeParameters.Concat(method0.TypeParameters).Take(totalSkipCount).Select(p => p.Name))); + } +#endif + } + return true; + } + void FindUseSites(ILFunction function, ILTransformContext context, Dictionary localFunctions) { foreach (var inst in function.Body.Descendants) { @@ -131,13 +181,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms void HandleUseSite(IMethod targetMethod, CallInstruction inst) { if (!localFunctions.TryGetValue((MethodDefinitionHandle)targetMethod.MetadataToken, out var info)) { - targetMethod = (IMethod)targetMethod.MemberDefinition; context.StepStartGroup($"Read local function '{targetMethod.Name}'", inst); info = new LocalFunctionInfo() { UseSites = new List() { inst }, - Method = targetMethod, - Definition = ReadLocalFunctionDefinition(context.Function, targetMethod) + Method = (IMethod)targetMethod.MemberDefinition, }; + var rootFunction = context.Function; + int skipCount = GetSkipCount(rootFunction, targetMethod); + info.Definition = ReadLocalFunctionDefinition(rootFunction, targetMethod, skipCount); localFunctions.Add((MethodDefinitionHandle)targetMethod.MetadataToken, info); if (info.Definition != null) { FindUseSites(info.Definition, context, localFunctions); @@ -149,14 +200,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - ILFunction ReadLocalFunctionDefinition(ILFunction rootFunction, IMethod targetMethod) + ILFunction ReadLocalFunctionDefinition(ILFunction rootFunction, IMethod targetMethod, int skipCount) { var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken); if (!methodDefinition.HasBody()) return null; var ilReader = context.CreateILReader(); var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); - var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, default, ILFunctionKind.LocalFunction, context.CancellationToken); + var genericContext = GenericContextFromTypeArguments(targetMethod, skipCount); + if (genericContext == null) + return null; + var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.GetValueOrDefault(), ILFunctionKind.LocalFunction, context.CancellationToken); // Embed the local function into the parent function's ILAst, so that "Show steps" can show // how the local function body is being transformed. rootFunction.LocalFunctions.Add(function); @@ -165,10 +219,64 @@ namespace ICSharpCode.Decompiler.IL.Transforms var nestedContext = new ILTransformContext(context, function); function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is LocalFunctionDecompiler)), nestedContext); function.DeclarationScope = null; - function.ReducedMethod = ReduceToLocalFunction(targetMethod); + function.ReducedMethod = ReduceToLocalFunction(function.Method); + function.ReducedMethod.NumberOfCompilerGeneratedGenerics = skipCount; return function; } + int GetSkipCount(ILFunction rootFunction, IMethod targetMethod) + { + targetMethod = (IMethod)targetMethod.MemberDefinition; + var skipCount = rootFunction.Method.DeclaringType.TypeParameters.Count + rootFunction.Method.TypeParameters.Count - targetMethod.DeclaringType.TypeParameters.Count; + if (skipCount < 0) { + skipCount = 0; + } + if (targetMethod.TypeParameters.Count > 0) { + var lastParams = targetMethod.Parameters.Where(p => IsClosureParameter(p, this.resolveContext)).SelectMany(p => UnwrapByRef(p.Type).TypeArguments) + .Select(pt => (int?)targetMethod.TypeArguments.IndexOf(pt)).DefaultIfEmpty().Max(); + if (lastParams != null && lastParams.GetValueOrDefault() + 1 > skipCount) + skipCount = lastParams.GetValueOrDefault() + 1; + } + return skipCount; + } + + static TypeSystem.GenericContext? GenericContextFromTypeArguments(IMethod targetMethod, int skipCount) + { + if (skipCount < 0 || skipCount > targetMethod.TypeParameters.Count) { + Debug.Assert(false); + return null; + } + int total = targetMethod.DeclaringType.TypeParameters.Count + skipCount; + if (total == 0) + return default(TypeSystem.GenericContext); + + var classTypeParameters = new List(targetMethod.DeclaringType.TypeParameters); + var methodTypeParameters = new List(targetMethod.TypeParameters); + var a = targetMethod.DeclaringType.TypeArguments.Concat(targetMethod.TypeArguments).Take(total); + int idx = 0; + foreach (var curA in a) { + int curIdx; + List curParameters; + IReadOnlyList curArgs; + if (idx < classTypeParameters.Count) { + curIdx = idx; + curParameters = classTypeParameters; + curArgs = targetMethod.DeclaringType.TypeArguments; + } else { + curIdx = idx - classTypeParameters.Count; + curParameters = methodTypeParameters; + curArgs = targetMethod.TypeArguments; + } + if (curArgs[curIdx].Kind != TypeKind.TypeParameter) + break; + curParameters[curIdx] = (ITypeParameter)curA; + idx++; + } + Debug.Assert(idx == total); + + return new TypeSystem.GenericContext(classTypeParameters, methodTypeParameters); + } + static T FindCommonAncestorInstruction(ILInstruction a, ILInstruction b) where T : ILInstruction { @@ -219,19 +327,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms { useSite.Arguments[0].ReplaceWith(new LdNull().WithILRange(useSite.Arguments[0])); var fnptr = (IInstructionWithMethodOperand)useSite.Arguments[1]; - LocalFunctionMethod reducedMethod = function.ReducedMethod; - if (reducedMethod.TypeParameters.Count > 0) - reducedMethod = new LocalFunctionMethod(fnptr.Method, reducedMethod.NumberOfCompilerGeneratedParameters); - var replacement = new LdFtn(reducedMethod).WithILRange((ILInstruction)fnptr); + var specializeMethod = function.ReducedMethod.Specialize(fnptr.Method.Substitution); + var replacement = new LdFtn(specializeMethod).WithILRange((ILInstruction)fnptr); useSite.Arguments[1].ReplaceWith(replacement); } void TransformToLocalFunctionInvocation(LocalFunctionMethod reducedMethod, CallInstruction useSite) { - if (reducedMethod.TypeParameters.Count > 0) - reducedMethod = new LocalFunctionMethod(useSite.Method, reducedMethod.NumberOfCompilerGeneratedParameters); + var specializeMethod = reducedMethod.Specialize(useSite.Method.Substitution); bool wasInstanceCall = !useSite.Method.IsStatic; - var replacement = new Call(reducedMethod); + var replacement = new Call(specializeMethod); int firstArgumentIndex = wasInstanceCall ? 1 : 0; int argumentCount = useSite.Arguments.Count; int reducedArgumentCount = argumentCount - (reducedMethod.NumberOfCompilerGeneratedParameters + firstArgumentIndex); diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs index cb53d51a6..6475bae1a 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs @@ -67,6 +67,10 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation internal int NumberOfCompilerGeneratedParameters { get; } + internal int NumberOfCompilerGeneratedGenerics { get; set; } + + internal bool IsStaticLocalFunction => NumberOfCompilerGeneratedParameters == 0 && baseMethod.IsStatic; + public IMember MemberDefinition => this; public IType ReturnType => baseMethod.ReturnType; From 33f96fd8888f1a5edf2a3510500ea363c6392c57 Mon Sep 17 00:00:00 2001 From: SilverFox Date: Thu, 14 Nov 2019 23:30:31 +0800 Subject: [PATCH 08/15] Fix tests `LocalFunctions.NestedCapture1` --- .../TestCases/Pretty/LocalFunctions.cs | 8 ++++++-- .../IL/Transforms/LocalFunctionDecompiler.cs | 3 +-- .../IL/Transforms/TransformDisplayClassUsage.cs | 14 +++++++------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 17c7ae6e1..5d18a7ebd 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -502,11 +502,15 @@ namespace LocalFunctions { return Method1_1; - void Method1_1(object containerBuilder) => + void Method1_1(object containerBuilder) + { Method1_2(containerBuilder); + } - void Method1_2(object containerBuilder) => + void Method1_2(object containerBuilder) + { action(containerBuilder); + } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index f71ccf4ce..b6a828b81 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -379,8 +379,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (closureVar.Kind == VariableKind.NamedArgument) return false; - ITypeDefinition potentialDisplayClass = UnwrapByRef(closureVar.Type).GetDefinition(); - if (!TransformDisplayClassUsage.IsPotentialClosure(context, potentialDisplayClass)) + if (!TransformDisplayClassUsage.IsClosure(context, closureVar, null, out _, out _)) return false; if (i - firstArgumentIndex >= 0) { Debug.Assert(i - firstArgumentIndex < function.Method.Parameters.Count && IsClosureParameter(function.Method.Parameters[i - firstArgumentIndex], resolveContext)); diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs index c8384aac7..ca77a44c4 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -59,7 +59,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms foreach (var v in f.Variables.ToArray()) { if (context.Settings.YieldReturn && HandleMonoStateMachine(function, v, decompilationContext, f)) continue; - if ((context.Settings.AnonymousMethods || context.Settings.ExpressionTrees) && IsClosure(v, out ITypeDefinition closureType, out var inst)) { + if ((context.Settings.AnonymousMethods || context.Settings.ExpressionTrees) && IsClosure(context, v, instructionsToRemove, out ITypeDefinition closureType, out var inst)) { AddOrUpdateDisplayClass(f, v, closureType, inst, localFunctionClosureParameter: false); } if (context.Settings.LocalFunctions && f.Kind == ILFunctionKind.LocalFunction && v.Kind == VariableKind.Parameter && v.Index > -1 && f.Method.Parameters[v.Index.Value] is IParameter p && LocalFunctionDecompiler.IsClosureParameter(p, decompilationContext)) { @@ -110,30 +110,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - bool IsClosure(ILVariable variable, out ITypeDefinition closureType, out ILInstruction initializer) + internal static bool IsClosure(ILTransformContext context, ILVariable variable, List instructionsToRemove, out ITypeDefinition closureType, out ILInstruction initializer) { closureType = null; initializer = null; if (variable.IsSingleDefinition && variable.StoreInstructions.SingleOrDefault() is StLoc inst) { initializer = inst; - if (IsClosureInit(inst, out closureType)) { - instructionsToRemove.Add(inst); + if (IsClosureInit(context, inst, out closureType)) { + instructionsToRemove?.Add(inst); return true; } } closureType = variable.Type.GetDefinition(); - if (context.Settings.LocalFunctions && closureType?.Kind == TypeKind.Struct && variable.HasInitialValue && IsPotentialClosure(this.context, closureType)) { + if (context.Settings.LocalFunctions && closureType?.Kind == TypeKind.Struct && variable.HasInitialValue && IsPotentialClosure(context, closureType)) { initializer = LocalFunctionDecompiler.GetStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First()); return true; } return false; } - bool IsClosureInit(StLoc inst, out ITypeDefinition closureType) + static bool IsClosureInit(ILTransformContext context, StLoc inst, out ITypeDefinition closureType) { if (inst.Value is NewObj newObj) { closureType = newObj.Method.DeclaringTypeDefinition; - return closureType != null && IsPotentialClosure(this.context, newObj); + return closureType != null && IsPotentialClosure(context, newObj); } closureType = null; return false; From 906efc3d9492aaa05cfa6ab72453ce9ef354348b Mon Sep 17 00:00:00 2001 From: SilverFox Date: Thu, 14 Nov 2019 23:35:59 +0800 Subject: [PATCH 09/15] Disabled tests `LocalFunctions.NestedCapture2` --- ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 5d18a7ebd..296197f2b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -514,6 +514,7 @@ namespace LocalFunctions } } +#if false public int NestedCapture2() { return Method(); @@ -545,5 +546,6 @@ namespace LocalFunctions } } } +#endif } } From 22daaa3572aab224f35d90ad7c8e525f3c12cd53 Mon Sep 17 00:00:00 2001 From: SilverFox Date: Fri, 15 Nov 2019 08:15:29 +0800 Subject: [PATCH 10/15] Enable static local functions and update related tests, since roslyn 3.4.0-beta3 is available for tests --- .../TestCases/Pretty/Async.cs | 4 + .../TestCases/Pretty/CustomTaskType.cs | 4 + .../TestCases/Pretty/LocalFunctions.cs | 116 ++++++++++++++++++ ICSharpCode.Decompiler/DecompilerSettings.cs | 2 +- .../IL/Transforms/DelegateConstruction.cs | 2 +- 5 files changed, 126 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs index 746925b6e..39ffaa55c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs @@ -190,7 +190,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { return await Nested(1) + await Nested(2); +#if CS80 + static async Task Nested(int i) +#else async Task Nested(int i) +#endif { await Task.Delay(i); return i; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs index 966b26cb5..01b70b6c5 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs @@ -118,7 +118,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { return await Nested(1) + await Nested(2); +#if CS80 + static async ValueTask Nested(int i) +#else async ValueTask Nested(int i) +#endif { await Task.Delay(i); return i; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 296197f2b..fc3cbfe2d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -48,25 +48,45 @@ namespace LocalFunctions { return GetHashCode(); } +#if CS80 + static int StaticMethod1() where T3 : struct +#else int StaticMethod1() where T3 : struct +#endif { return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticMethod1() + StaticMethod1_1() + StaticMethod2_RepeatT2(); } +#if CS80 + static int StaticMethod1_1() where T3 : struct where T4 : Enum +#else int StaticMethod1_1() where T3 : struct where T4 : Enum +#endif { return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + typeof(T4).Name.Length + StaticMethod1() + StaticMethod1_1(); } #pragma warning disable CS8387 +#if CS80 + static int StaticMethod2_RepeatT2() where T2 : IConvertible where T3 : struct where T4 : Enum +#else int StaticMethod2_RepeatT2() where T2 : IConvertible where T3 : struct where T4 : Enum +#endif #pragma warning restore CS8387 { return typeof(T2).Name.Length; } +#if CS80 + static int StaticMethod4(T dd) +#else int StaticMethod4(T dd) +#endif { return 0; } +#if CS80 + static int StaticMethod5() +#else int StaticMethod5() +#endif { int k = 0; return k + NonStaticMethod5_1(); @@ -100,33 +120,61 @@ namespace LocalFunctions { return GetHashCode(); } +#if CS80 + static int StaticInvokeAsFunc(Func func) +#else int StaticInvokeAsFunc(Func func) +#endif { return func(); } +#if CS80 + static int StaticInvokeAsFunc2(Func func) +#else int StaticInvokeAsFunc2(Func func) +#endif { return func(default(T)); } +#if CS80 + static int StaticMethod1() where T3 : struct +#else int StaticMethod1() where T3 : struct +#endif { return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticInvokeAsFunc(StaticMethod1) + StaticInvokeAsFunc(StaticMethod1_1) + StaticInvokeAsFunc(StaticMethod2_RepeatT2); } +#if CS80 + static int StaticMethod1_1() where T3 : struct where T4 : Enum +#else int StaticMethod1_1() where T3 : struct where T4 : Enum +#endif { return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + typeof(T4).Name.Length + StaticInvokeAsFunc(StaticMethod1) + StaticInvokeAsFunc(StaticMethod1_1); } #pragma warning disable CS8387 +#if CS80 + static int StaticMethod2_RepeatT2() where T2 : IConvertible where T3 : struct where T4 : Enum +#else int StaticMethod2_RepeatT2() where T2 : IConvertible where T3 : struct where T4 : Enum +#endif #pragma warning restore CS8387 { return typeof(T2).Name.Length; } +#if CS80 + static int StaticMethod4(T dd) +#else int StaticMethod4(T dd) +#endif { return 0; } +#if CS80 + static int StaticMethod5() +#else int StaticMethod5() +#endif { int k = 0; return k + StaticInvokeAsFunc(NonStaticMethod5_1); @@ -160,10 +208,18 @@ namespace LocalFunctions { ZZ(null); ZZ2(null); +#if CS80 + static void Nop(T data) +#else void Nop(T data) +#endif { } +#if CS80 + static void ZZ(T3 t3) where T3 : T2 +#else void ZZ(T3 t3) where T3 : T2 +#endif { Nop>(t3); ZZ1(t3); @@ -173,11 +229,19 @@ namespace LocalFunctions Nop>(t3); } } +#if CS80 + static void ZZ1(T3 t3) +#else void ZZ1(T3 t3) +#endif { Nop>((List)(object)t3); } +#if CS80 + static void ZZ2(T3 t3) +#else void ZZ2(T3 t3) +#endif { Nop>((List)(object)t3); } @@ -189,7 +253,11 @@ namespace LocalFunctions private Lazy nonCapturinglocalFunctionInLambda = new Lazy(delegate { return CreateValue(); +#if CS80 + static object CreateValue() +#else object CreateValue() +#endif { return null; } @@ -230,7 +298,11 @@ namespace LocalFunctions LocalWrite("Hello " + i); } +#if CS80 + static void LocalWrite(string s) +#else void LocalWrite(string s) +#endif { Console.WriteLine(s); } @@ -266,7 +338,11 @@ namespace LocalFunctions LocalWrite("Hello " + i); } +#if CS80 + static void LocalWrite(string s) +#else void LocalWrite(string s) +#endif { Console.WriteLine(s); } @@ -327,7 +403,11 @@ namespace LocalFunctions Test(5); LocalFunctions.Test(2); +#if CS80 + static void Test(int x) +#else void Test(int x) +#endif { Console.WriteLine("x: {0}", x); } @@ -344,7 +424,11 @@ namespace LocalFunctions Name(); action(); +#if CS80 + static void Name() +#else void Name() +#endif { } @@ -355,12 +439,20 @@ namespace LocalFunctions Use(Get(1), Get(2), Get(3)); Use(Get(1), c: Get(2), b: Get(3)); +#if CS80 + static int Get(int i) +#else int Get(int i) +#endif { return i; } +#if CS80 + static void Use(int a, int b, int c) +#else void Use(int a, int b, int c) +#endif { Console.WriteLine(a + b + c); } @@ -393,7 +485,11 @@ namespace LocalFunctions { return FibHelper(i); +#if CS80 + static int FibHelper(int n) +#else int FibHelper(int n) +#endif { if (n <= 0) { return 0; @@ -406,7 +502,11 @@ namespace LocalFunctions { return B(4) + C(3); +#if CS80 + static int A(int i) +#else int A(int i) +#endif { if (i > 0) { return A(i - 1) + 2 * B(i - 1) + 3 * C(i - 1); @@ -414,7 +514,11 @@ namespace LocalFunctions return 1; } +#if CS80 + static int B(int i) +#else int B(int i) +#endif { if (i > 0) { return 3 * A(i - 1) + B(i - 1); @@ -422,7 +526,11 @@ namespace LocalFunctions return 1; } +#if CS80 + static int C(int i) +#else int C(int i) +#endif { if (i > 0) { return 2 * A(i - 1) + C(i - 1); @@ -498,7 +606,11 @@ namespace LocalFunctions { Method1(null); +#if CS80 + static Action Method1(Action action) +#else Action Method1(Action action) +#endif { return Method1_1; @@ -518,7 +630,11 @@ namespace LocalFunctions public int NestedCapture2() { return Method(); +#if CS80 + static int Method() +#else int Method() +#endif { int t0 = 0; return ZZZ_0(); diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 94825477a..1cf0ce8e0 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -1065,7 +1065,7 @@ namespace ICSharpCode.Decompiler } } - bool staticLocalFunctions = false; + bool staticLocalFunctions = true; /// /// Gets/Sets whether C# 8.0 static local functions should be transformed. diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index 4d723a0b6..07da7dd7b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -95,7 +95,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; } - internal static GenericContext? GenericContextFromTypeArguments(TypeParameterSubstitution subst) + static GenericContext? GenericContextFromTypeArguments(TypeParameterSubstitution subst) { var classTypeParameters = new List(); var methodTypeParameters = new List(); From 14cd09d7bc5aef0cc1465b99a03605ce1893ff33 Mon Sep 17 00:00:00 2001 From: SilverFox Date: Fri, 15 Nov 2019 13:24:04 +0800 Subject: [PATCH 11/15] Update some tests, and add some limited descributed in PR to tests(disabled) --- .../TestCases/Pretty/DelegateConstruction.cs | 4 ++ .../TestCases/Pretty/LocalFunctions.cs | 56 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs index 86ec9d1cc..bb522de53 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs @@ -187,7 +187,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty Noop("M3", this.M3); Noop("M3", M3); +#if CS80 + static void M3() +#else void M3() +#endif { } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index fc3cbfe2d..28cbc558a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -246,6 +246,62 @@ namespace LocalFunctions Nop>((List)(object)t3); } } + +#if false + public void GenericArgsWithAnonymousType() + { + Method(); +#if CS80 + static void Method() +#else + void Method() +#endif + { + int i = 0; + var obj2 = new { + A = 1 + }; + Method2(obj2); + Method3(obj2); + void Method2(T3 obj1) + { + //keep nested + i = 0; + } +#if CS80 + static void Method3(T3 obj1) +#else + void Method3(T3 obj1) +#endif + { + } + } + } +#if CS80 + public void NameConflict() + { + int i = 0; + Method(); + void Method() + { + Method(); + void Method() + { + Method(); + i = 0; + void Method() + { + i = 0; + Method(); + static void Method() + { + } + } + } + } + } +#endif +#endif } private int field; From a5d85fea3fe5bd796db98cad0cc661173c7e5690 Mon Sep 17 00:00:00 2001 From: SilverFox Date: Sat, 16 Nov 2019 11:22:17 +0800 Subject: [PATCH 12/15] Add an workaround for #1798 --- .../TestCases/Pretty/LocalFunctions.cs | 24 +++--- .../IL/Transforms/LocalFunctionDecompiler.cs | 74 ++++++++++--------- 2 files changed, 53 insertions(+), 45 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 28cbc558a..013111e78 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -682,7 +682,6 @@ namespace LocalFunctions } } -#if false public int NestedCapture2() { return Method(); @@ -697,27 +696,28 @@ namespace LocalFunctions int ZZZ_0() { t0 = 0; - var t1 = t0; - Func zzz2 = () => { + int t2 = t0; + return new Func(ZZZ_0_0)(); + int ZZZ_0_0() + { t0 = 0; - t1 = 0; + t2 = 0; return ZZZ_1(); - }; - return zzz2(); + } } int ZZZ_1() { t0 = 0; - var t1 = t0; - Func zzz = () => { + int t = t0; + return new Func(ZZZ_1_0)(); + int ZZZ_1_0() + { t0 = 0; - t1 = 0; + t = 0; return 0; - }; - return zzz(); + } } } } -#endif } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index b6a828b81..d9383a5d8 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -84,9 +84,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms try { var localFunction = info.Definition; if (!localFunction.Method.IsStatic) { - var target = info.UseSites[0].Arguments[0]; - context.Step($"Replace 'this' with {target}", localFunction); var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); + var target = info.UseSites.Where(us => us.Arguments[0].MatchLdLoc(out _)).FirstOrDefault()?.Arguments[0]; + if (target == null) { + target = info.UseSites[0].Arguments[0]; + if (target.MatchLdObj(out var target1, out var type) && target1 is LdFlda && thisVar.Type == type && type.Kind == TypeKind.Class && TransformDisplayClassUsage.IsPotentialClosure(context, type.GetDefinition())) { + var variable = function.Descendants.OfType().SelectMany(f => f.Variables).Where(v => !v.IsThis() && TransformDisplayClassUsage.IsClosure(context, v, null, out var varType, out _) && varType == type).OnlyOrDefault(); + if (variable != null) { + target = new LdLoc(variable); + HandleArgument(localFunction, 1, 0, target); + } + } + } + context.Step($"Replace 'this' with {target}", localFunction); localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); } @@ -141,7 +151,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (skipCount > 0) return false; skipCount += parentMethod.TypeParameters.Count; - Debug.Assert(skipCount >= 0 && skipCount <= method.TypeArguments.Count); if (skipCount < 0 || skipCount > method.TypeArguments.Count) return false; @@ -272,7 +281,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms curParameters[curIdx] = (ITypeParameter)curA; idx++; } - Debug.Assert(idx == total); + if (idx != total) { + Debug.Assert(false); + return null; + } return new TypeSystem.GenericContext(classTypeParameters, methodTypeParameters); } @@ -365,41 +377,37 @@ namespace ICSharpCode.Decompiler.IL.Transforms { int firstArgumentIndex = function.Method.IsStatic ? 0 : 1; for (int i = useSite.Arguments.Count - 1; i >= firstArgumentIndex; i--) { - if (!HandleArgument(i, useSite.Arguments[i])) + if (!HandleArgument(function, firstArgumentIndex, i, useSite.Arguments[i])) break; } if (firstArgumentIndex > 0) { - HandleArgument(0, useSite.Arguments[0]); + HandleArgument(function, firstArgumentIndex, 0, useSite.Arguments[0]); } + } - bool HandleArgument(int i, ILInstruction arg) - { - ILVariable closureVar; - if (!(arg.MatchLdLoc(out closureVar) || arg.MatchLdLoca(out closureVar))) - return false; - if (closureVar.Kind == VariableKind.NamedArgument) - return false; - if (!TransformDisplayClassUsage.IsClosure(context, closureVar, null, out _, out _)) - return false; - if (i - firstArgumentIndex >= 0) { - Debug.Assert(i - firstArgumentIndex < function.Method.Parameters.Count && IsClosureParameter(function.Method.Parameters[i - firstArgumentIndex], resolveContext)); - } - if (closureVar.AddressCount == 0 && closureVar.StoreInstructions.Count == 0) - return true; - // determine the capture scope of closureVar and the declaration scope of the function - var instructions = closureVar.StoreInstructions.OfType() - .Concat(closureVar.AddressInstructions).OrderBy(inst => inst.StartILOffset).ToList(); - var additionalScope = BlockContainer.FindClosestContainer(instructions.First()); - if (closureVar.CaptureScope == null) - closureVar.CaptureScope = additionalScope; - else - closureVar.CaptureScope = FindCommonAncestorInstruction(closureVar.CaptureScope, additionalScope); - if (function.DeclarationScope == null) - function.DeclarationScope = closureVar.CaptureScope; - else if (!IsInNestedLocalFunction(function.DeclarationScope, closureVar.CaptureScope.Ancestors.OfType().First())) - function.DeclarationScope = FindCommonAncestorInstruction(function.DeclarationScope, closureVar.CaptureScope); - return true; + bool HandleArgument(ILFunction function, int firstArgumentIndex, int i, ILInstruction arg) + { + ILVariable closureVar; + if (!(arg.MatchLdLoc(out closureVar) || arg.MatchLdLoca(out closureVar))) + return false; + if (closureVar.Kind == VariableKind.NamedArgument) + return false; + if (!TransformDisplayClassUsage.IsClosure(context, closureVar, null, out _, out var initializer)) + return false; + if (i - firstArgumentIndex >= 0) { + Debug.Assert(i - firstArgumentIndex < function.Method.Parameters.Count && IsClosureParameter(function.Method.Parameters[i - firstArgumentIndex], resolveContext)); } + // determine the capture scope of closureVar and the declaration scope of the function + var additionalScope = BlockContainer.FindClosestContainer(initializer); + if (closureVar.CaptureScope == null) + closureVar.CaptureScope = additionalScope; + else + closureVar.CaptureScope = FindCommonAncestorInstruction(closureVar.CaptureScope, additionalScope); + if (function.DeclarationScope == null) + function.DeclarationScope = closureVar.CaptureScope; + else if (!IsInNestedLocalFunction(function.DeclarationScope, closureVar.CaptureScope.Ancestors.OfType().First())) + function.DeclarationScope = FindCommonAncestorInstruction(function.DeclarationScope, closureVar.CaptureScope); + return true; } bool IsInNestedLocalFunction(BlockContainer declarationScope, ILFunction function) From f69d0cc5d0d193b7830ef49891bd843b5e95ce09 Mon Sep 17 00:00:00 2001 From: SilverFox Date: Sat, 16 Nov 2019 11:28:30 +0800 Subject: [PATCH 13/15] Add support of static local function delegate for roslyn < 16.4 --- .../TypeSystem/Implementation/LocalFunctionMethod.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs index 6475bae1a..023153ae8 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using ICSharpCode.Decompiler.Util; @@ -42,7 +43,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation if (!(obj is LocalFunctionMethod other)) return false; return baseMethod.Equals(other.baseMethod, typeNormalization) - && NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters; + && NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters + && NumberOfCompilerGeneratedGenerics == other.NumberOfCompilerGeneratedGenerics; } public override bool Equals(object obj) @@ -50,7 +52,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation if (!(obj is LocalFunctionMethod other)) return false; return baseMethod.Equals(other.baseMethod) - && NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters; + && NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters + && NumberOfCompilerGeneratedGenerics == other.NumberOfCompilerGeneratedGenerics; } public override int GetHashCode() @@ -62,14 +65,14 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public override string ToString() { - return string.Format("[LocalFunctionMethod: ReducedFrom={0}, NumberOfGeneratedParameters={1}]", ReducedFrom, NumberOfCompilerGeneratedParameters); + return string.Format("[LocalFunctionMethod: ReducedFrom={0}, NumberOfGeneratedParameters={1}, NumberOfCompilerGeneratedGenerics={2}]", ReducedFrom, NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedGenerics); } internal int NumberOfCompilerGeneratedParameters { get; } internal int NumberOfCompilerGeneratedGenerics { get; set; } - internal bool IsStaticLocalFunction => NumberOfCompilerGeneratedParameters == 0 && baseMethod.IsStatic; + internal bool IsStaticLocalFunction => NumberOfCompilerGeneratedParameters == 0 && (baseMethod.IsStatic || (baseMethod.DeclaringTypeDefinition.IsCompilerGenerated() && !baseMethod.DeclaringType.GetFields(f => !f.IsStatic).Any())); public IMember MemberDefinition => this; From 9e89384916d1080bd04540eefbf59313d8916d3f Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 19 Dec 2019 15:54:36 +0100 Subject: [PATCH 14/15] Make NumberOfCompilerGeneratedGenerics immutable and rename to NumberOfCompilerGeneratedTypeParameters --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 4 ++-- .../CSharp/StatementBuilder.cs | 2 +- .../IL/Transforms/LocalFunctionDecompiler.cs | 9 ++++----- .../Implementation/LocalFunctionMethod.cs | 20 ++++++++++--------- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 0d6fde5da..a2ca47fe1 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -197,7 +197,7 @@ namespace ICSharpCode.Decompiler.CSharp } else if (localFunction != null) { var ide = new IdentifierExpression(localFunction.Name); if (method.TypeArguments.Count > 0) { - int skipCount = localFunction.ReducedMethod.NumberOfCompilerGeneratedGenerics; + int skipCount = localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters; ide.TypeArguments.AddRange(method.TypeArguments.Skip(skipCount).Select(expressionBuilder.ConvertType)); } target = ide.WithoutILInstruction() @@ -1401,7 +1401,7 @@ namespace ICSharpCode.Decompiler.CSharp if ((step & 2) != 0) { int skipCount = 0; if (localFunction != null && method.TypeArguments.Count > 0) { - skipCount = localFunction.ReducedMethod.NumberOfCompilerGeneratedGenerics; + skipCount = localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters; } ide.TypeArguments.AddRange(method.TypeArguments.Skip(skipCount).Select(expressionBuilder.ConvertType)); } diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 1c58f6e92..5fa43f347 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -990,7 +990,7 @@ namespace ICSharpCode.Decompiler.CSharp if (function.Method.TypeParameters.Count > 0) { var astBuilder = exprBuilder.astBuilder; if (astBuilder.ShowTypeParameters) { - int skipCount = function.ReducedMethod.NumberOfCompilerGeneratedGenerics; + int skipCount = function.ReducedMethod.NumberOfCompilerGeneratedTypeParameters; stmt.TypeParameters.AddRange(function.Method.TypeParameters.Skip(skipCount).Select(t => astBuilder.ConvertTypeParameter(t))); if (astBuilder.ShowTypeParameterConstraints) { stmt.Constraints.AddRange(function.Method.TypeParameters.Skip(skipCount).Select(t => astBuilder.ConvertTypeParameterConstraint(t)).Where(c => c != null)); diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index d9383a5d8..2e8d36bbd 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -115,7 +115,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms declaringFunction.LocalFunctions.Add(localFunction); } - if (TryValidateSkipCount(info, out int skipCount) && skipCount != localFunction.ReducedMethod.NumberOfCompilerGeneratedGenerics) { + if (TryValidateSkipCount(info, out int skipCount) && skipCount != localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters) { Debug.Assert(false); function.Warnings.Add($"Could not decode local function '{info.Method}'"); if (localFunction.DeclarationScope != function.Body && localFunction.DeclarationScope.Parent is ILFunction declaringFunction) { @@ -228,8 +228,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms var nestedContext = new ILTransformContext(context, function); function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is LocalFunctionDecompiler)), nestedContext); function.DeclarationScope = null; - function.ReducedMethod = ReduceToLocalFunction(function.Method); - function.ReducedMethod.NumberOfCompilerGeneratedGenerics = skipCount; + function.ReducedMethod = ReduceToLocalFunction(function.Method, skipCount); return function; } @@ -324,7 +323,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return inst; } - LocalFunctionMethod ReduceToLocalFunction(IMethod method) + LocalFunctionMethod ReduceToLocalFunction(IMethod method, int skipCount) { int parametersToRemove = 0; for (int i = method.Parameters.Count - 1; i >= 0; i--) { @@ -332,7 +331,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms break; parametersToRemove++; } - return new LocalFunctionMethod(method, parametersToRemove); + return new LocalFunctionMethod(method, parametersToRemove, skipCount); } static void TransformToLocalFunctionReference(ILFunction function, CallInstruction useSite) diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs index 023153ae8..9d07a120e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs @@ -31,20 +31,24 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation { readonly IMethod baseMethod; - public LocalFunctionMethod(IMethod baseMethod, int numberOfCompilerGeneratedParameters) + public LocalFunctionMethod(IMethod baseMethod, int numberOfCompilerGeneratedParameters, int numberOfCompilerGeneratedTypeParameters) { + if (baseMethod == null) + throw new ArgumentNullException(nameof(baseMethod)); + if (baseMethod is SpecializedMethod) + throw new ArgumentException("Must not be a specialized method!", nameof(baseMethod)); this.baseMethod = baseMethod; this.NumberOfCompilerGeneratedParameters = numberOfCompilerGeneratedParameters; + this.NumberOfCompilerGeneratedTypeParameters = numberOfCompilerGeneratedTypeParameters; } - public bool Equals(IMember obj, TypeVisitor typeNormalization) { if (!(obj is LocalFunctionMethod other)) return false; return baseMethod.Equals(other.baseMethod, typeNormalization) && NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters - && NumberOfCompilerGeneratedGenerics == other.NumberOfCompilerGeneratedGenerics; + && NumberOfCompilerGeneratedTypeParameters == other.NumberOfCompilerGeneratedTypeParameters; } public override bool Equals(object obj) @@ -53,24 +57,22 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation return false; return baseMethod.Equals(other.baseMethod) && NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters - && NumberOfCompilerGeneratedGenerics == other.NumberOfCompilerGeneratedGenerics; + && NumberOfCompilerGeneratedTypeParameters == other.NumberOfCompilerGeneratedTypeParameters; } public override int GetHashCode() { - unchecked { - return baseMethod.GetHashCode() + NumberOfCompilerGeneratedParameters + 1; - } + return baseMethod.GetHashCode(); } public override string ToString() { - return string.Format("[LocalFunctionMethod: ReducedFrom={0}, NumberOfGeneratedParameters={1}, NumberOfCompilerGeneratedGenerics={2}]", ReducedFrom, NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedGenerics); + return string.Format("[LocalFunctionMethod: ReducedFrom={0}, NumberOfGeneratedParameters={1}, NumberOfCompilerGeneratedTypeParameters={2}]", ReducedFrom, NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedTypeParameters); } internal int NumberOfCompilerGeneratedParameters { get; } - internal int NumberOfCompilerGeneratedGenerics { get; set; } + internal int NumberOfCompilerGeneratedTypeParameters { get; } internal bool IsStaticLocalFunction => NumberOfCompilerGeneratedParameters == 0 && (baseMethod.IsStatic || (baseMethod.DeclaringTypeDefinition.IsCompilerGenerated() && !baseMethod.DeclaringType.GetFields(f => !f.IsStatic).Any())); From 5be6be97df974a314ad0f90a40974b4223b373c2 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 19 Dec 2019 17:35:15 +0100 Subject: [PATCH 15/15] Code review: * rename a few identifiers, * add comments, * skip all compiler-generated type-arguments in resolve result, * and change implementation of LocalFunctionMethod.Specialize: wrap specialized method and not the other way round. --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 2 +- .../IL/Transforms/LocalFunctionDecompiler.cs | 30 ++++++++++++------- .../Implementation/LocalFunctionMethod.cs | 6 ++-- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index a2ca47fe1..267a757fe 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1462,7 +1462,7 @@ namespace ICSharpCode.Decompiler.CSharp method.DeclaringType, new IParameterizedMember[] { method } ) - }, method.TypeArguments + }, method.TypeArguments.Skip(localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters).ToArray() ); } diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index 2e8d36bbd..31efc2500 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -53,18 +53,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// /// local functions can either be used in method calls, i.e., call and callvirt instructions, - /// or can be used as part of the "delegate construction" pattern, i.e., newobj Delegate(<target-expression>, ldftn <method>). + /// or can be used as part of the "delegate construction" pattern, i.e., + /// newobj Delegate(<target-expression>, ldftn <method>). /// - /// As local functions can be declared practically anywhere, we have to take a look at all use-sites and infer the declaration location from that. Use-sites can be call, callvirt and ldftn instructions. - /// After all use-sites are collected we construct the ILAst of the local function and add it to the parent function. - /// Then all use-sites of the local-function are transformed to a call to the LocalFunctionMethod or a ldftn of the LocalFunctionMethod. + /// As local functions can be declared practically anywhere, we have to take a look at + /// all use-sites and infer the declaration location from that. Use-sites can be call, + /// callvirt and ldftn instructions. + /// After all use-sites are collected we construct the ILAst of the local function + /// and add it to the parent function. + /// Then all use-sites of the local-function are transformed to a call to the + /// LocalFunctionMethod or a ldftn of the LocalFunctionMethod. /// In a next step we handle all nested local functions. - /// After all local functions are transformed, we move all local functions that capture any variables to their respective declaration scope. + /// After all local functions are transformed, we move all local functions that capture + /// any variables to their respective declaration scope. /// public void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.LocalFunctions) return; + // Disable the transform if we are decompiling a display-class or local function method: + // This happens if a local function or display class is selected in the ILSpy tree view. if (IsLocalFunctionMethod(function.Method, context) || IsLocalFunctionDisplayClass(function.Method.ParentModule.PEFile, (TypeDefinitionHandle)function.Method.DeclaringTypeDefinition.MetadataToken, context)) return; this.context = context; @@ -88,8 +96,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms var target = info.UseSites.Where(us => us.Arguments[0].MatchLdLoc(out _)).FirstOrDefault()?.Arguments[0]; if (target == null) { target = info.UseSites[0].Arguments[0]; - if (target.MatchLdObj(out var target1, out var type) && target1 is LdFlda && thisVar.Type == type && type.Kind == TypeKind.Class && TransformDisplayClassUsage.IsPotentialClosure(context, type.GetDefinition())) { - var variable = function.Descendants.OfType().SelectMany(f => f.Variables).Where(v => !v.IsThis() && TransformDisplayClassUsage.IsClosure(context, v, null, out var varType, out _) && varType == type).OnlyOrDefault(); + if (target.MatchLdFld(out var target1, out var field) && thisVar.Type.Equals(field.Type) && field.Type.Kind == TypeKind.Class && TransformDisplayClassUsage.IsPotentialClosure(context, field.Type.GetDefinition())) { + var variable = function.Descendants.OfType().SelectMany(f => f.Variables).Where(v => !v.IsThis() && TransformDisplayClassUsage.IsClosure(context, v, null, out var varType, out _) && varType.Equals(field.Type)).OnlyOrDefault(); if (variable != null) { target = new LdLoc(variable); HandleArgument(localFunction, 1, 0, target); @@ -241,7 +249,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (targetMethod.TypeParameters.Count > 0) { var lastParams = targetMethod.Parameters.Where(p => IsClosureParameter(p, this.resolveContext)).SelectMany(p => UnwrapByRef(p.Type).TypeArguments) - .Select(pt => (int?)targetMethod.TypeArguments.IndexOf(pt)).DefaultIfEmpty().Max(); + .Select(pt => (int?)targetMethod.TypeParameters.IndexOf(pt)).DefaultIfEmpty().Max(); if (lastParams != null && lastParams.GetValueOrDefault() + 1 > skipCount) skipCount = lastParams.GetValueOrDefault() + 1; } @@ -260,9 +268,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms var classTypeParameters = new List(targetMethod.DeclaringType.TypeParameters); var methodTypeParameters = new List(targetMethod.TypeParameters); - var a = targetMethod.DeclaringType.TypeArguments.Concat(targetMethod.TypeArguments).Take(total); + var skippedTypeArguments = targetMethod.DeclaringType.TypeArguments.Concat(targetMethod.TypeArguments).Take(total); int idx = 0; - foreach (var curA in a) { + foreach (var skippedTA in skippedTypeArguments) { int curIdx; List curParameters; IReadOnlyList curArgs; @@ -277,7 +285,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (curArgs[curIdx].Kind != TypeKind.TypeParameter) break; - curParameters[curIdx] = (ITypeParameter)curA; + curParameters[curIdx] = (ITypeParameter)skippedTA; idx++; } if (idx != total) { diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs index 9d07a120e..242b6b193 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs @@ -35,8 +35,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation { if (baseMethod == null) throw new ArgumentNullException(nameof(baseMethod)); - if (baseMethod is SpecializedMethod) - throw new ArgumentException("Must not be a specialized method!", nameof(baseMethod)); this.baseMethod = baseMethod; this.NumberOfCompilerGeneratedParameters = numberOfCompilerGeneratedParameters; this.NumberOfCompilerGeneratedTypeParameters = numberOfCompilerGeneratedTypeParameters; @@ -88,7 +86,9 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public IMethod Specialize(TypeParameterSubstitution substitution) { - return SpecializedMethod.Create(this, substitution); + return new LocalFunctionMethod( + baseMethod.Specialize(substitution), + NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedTypeParameters); } IMember IMember.Specialize(TypeParameterSubstitution substitution)