Browse Source

Merge pull request #1797 from yyjdelete/localFunc

Add support for generic and static local function

Fixes #1588
pull/1880/head
Siegfried Pammer 6 years ago
parent
commit
f08a721e6a
  1. 4
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs
  2. 4
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs
  3. 4
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs
  4. 387
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs
  5. 40
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  6. 6
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  7. 13
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  8. 4
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
  9. 20
      ICSharpCode.Decompiler/DecompilerSettings.cs
  10. 1
      ICSharpCode.Decompiler/IL/ILReader.cs
  11. 2
      ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs
  12. 198
      ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs
  13. 14
      ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs
  14. 27
      ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs
  15. 9
      ILSpy/Properties/Resources.Designer.cs
  16. 3
      ILSpy/Properties/Resources.resx

4
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs

@ -190,7 +190,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
return await Nested(1) + await Nested(2); return await Nested(1) + await Nested(2);
#if CS80
static async Task<int> Nested(int i)
#else
async Task<int> Nested(int i) async Task<int> Nested(int i)
#endif
{ {
await Task.Delay(i); await Task.Delay(i);
return i; return i;

4
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CustomTaskType.cs

@ -118,7 +118,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
return await Nested(1) + await Nested(2); return await Nested(1) + await Nested(2);
#if CS80
static async ValueTask<int> Nested(int i)
#else
async ValueTask<int> Nested(int i) async ValueTask<int> Nested(int i)
#endif
{ {
await Task.Delay(i); await Task.Delay(i);
return i; return i;

4
ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs

@ -187,7 +187,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Noop("M3", this.M3); Noop("M3", this.M3);
Noop("M3", M3); Noop("M3", M3);
#if CS80
static void M3()
#else
void M3() void M3()
#endif
{ {
} }

387
ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs

@ -23,12 +23,297 @@ namespace LocalFunctions
{ {
internal class LocalFunctions internal class LocalFunctions
{ {
public class Generic<T1> where T1 : struct, ICloneable, IConvertible
{
public int MixedLocalFunction<T2>() where T2 : ICloneable, IConvertible
{
T2 t2 = default(T2);
object z = this;
for (int j = 0; j < 10; j++) {
int i = 0;
i += NonStaticMethod6<object>();
int NonStaticMethod6<T3>()
{
t2 = default(T2);
int l = 0;
return NonStaticMethod6_1<T1>() + NonStaticMethod6_1<T2>() + z.GetHashCode();
int NonStaticMethod6_1<T4>()
{
return i + l + NonStaticMethod6<T4>() + StaticMethod1<decimal>();
}
}
}
return MixedLocalFunction<T1>() + MixedLocalFunction<T2>() + StaticMethod1<decimal>() + StaticMethod1<int>() + NonStaticMethod3() + StaticMethod4<object>(null) + StaticMethod5<T1>();
int NonStaticMethod3()
{
return GetHashCode();
}
#if CS80
static int StaticMethod1<T3>() where T3 : struct
#else
int StaticMethod1<T3>() where T3 : struct
#endif
{
return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticMethod1<float>() + StaticMethod1_1<T3, DayOfWeek>() + StaticMethod2_RepeatT2<T2, T3, DayOfWeek>();
}
#if CS80
static int StaticMethod1_1<T3, T4>() where T3 : struct where T4 : Enum
#else
int StaticMethod1_1<T3, T4>() 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<float>() + StaticMethod1_1<T3, DayOfWeek>();
}
#pragma warning disable CS8387
#if CS80
static int StaticMethod2_RepeatT2<T2, T3, T4>() where T2 : IConvertible where T3 : struct where T4 : Enum
#else
int StaticMethod2_RepeatT2<T2, T3, T4>() 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>(T dd)
#else
int StaticMethod4<T>(T dd)
#endif
{
return 0;
}
#if CS80
static int StaticMethod5<T3>()
#else
int StaticMethod5<T3>()
#endif
{
int k = 0;
return k + NonStaticMethod5_1<T1>();
int NonStaticMethod5_1<T4>()
{
return k;
}
}
}
public int MixedLocalFunction2Delegate<T2>() where T2 : ICloneable, IConvertible
{
T2 t2 = default(T2);
object z = this;
for (int j = 0; j < 10; j++) {
int i = 0;
i += StaticInvokeAsFunc(NonStaticMethod6<object>);
int NonStaticMethod6<T3>()
{
t2 = default(T2);
int l = 0;
return StaticInvokeAsFunc(NonStaticMethod6_1<T1>) + StaticInvokeAsFunc(NonStaticMethod6_1<T2>) + z.GetHashCode();
int NonStaticMethod6_1<T4>()
{
return i + l + StaticInvokeAsFunc(NonStaticMethod6<T4>) + StaticInvokeAsFunc(StaticMethod1<decimal>);
}
}
}
return StaticInvokeAsFunc(MixedLocalFunction2Delegate<T1>) + StaticInvokeAsFunc(MixedLocalFunction2Delegate<T2>) + StaticInvokeAsFunc(StaticMethod1<decimal>) + StaticInvokeAsFunc(StaticMethod1<int>) + StaticInvokeAsFunc(NonStaticMethod3) + StaticInvokeAsFunc(StaticMethod5<T1>) + new Func<object, int>(StaticMethod4<object>)(null) + StaticInvokeAsFunc2<object>(StaticMethod4<object>) + new Func<Func<object, int>, int>(StaticInvokeAsFunc2<object>)(StaticMethod4<object>);
int NonStaticMethod3()
{
return GetHashCode();
}
#if CS80
static int StaticInvokeAsFunc(Func<int> func)
#else
int StaticInvokeAsFunc(Func<int> func)
#endif
{
return func();
}
#if CS80
static int StaticInvokeAsFunc2<T>(Func<T, int> func)
#else
int StaticInvokeAsFunc2<T>(Func<T, int> func)
#endif
{
return func(default(T));
}
#if CS80
static int StaticMethod1<T3>() where T3 : struct
#else
int StaticMethod1<T3>() where T3 : struct
#endif
{
return typeof(T1).Name.Length + typeof(T2).Name.Length + typeof(T3).Name.Length + StaticInvokeAsFunc(StaticMethod1<float>) + StaticInvokeAsFunc(StaticMethod1_1<T3, DayOfWeek>) + StaticInvokeAsFunc(StaticMethod2_RepeatT2<T2, T3, DayOfWeek>);
}
#if CS80
static int StaticMethod1_1<T3, T4>() where T3 : struct where T4 : Enum
#else
int StaticMethod1_1<T3, T4>() 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<float>) + StaticInvokeAsFunc(StaticMethod1_1<T3, DayOfWeek>);
}
#pragma warning disable CS8387
#if CS80
static int StaticMethod2_RepeatT2<T2, T3, T4>() where T2 : IConvertible where T3 : struct where T4 : Enum
#else
int StaticMethod2_RepeatT2<T2, T3, T4>() 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>(T dd)
#else
int StaticMethod4<T>(T dd)
#endif
{
return 0;
}
#if CS80
static int StaticMethod5<T3>()
#else
int StaticMethod5<T3>()
#endif
{
int k = 0;
return k + StaticInvokeAsFunc(NonStaticMethod5_1<T1>);
int NonStaticMethod5_1<T4>()
{
return k;
}
}
}
public static void Test_CaptureT<T2>()
{
T2 t2 = default(T2);
Method1<int>();
void Method1<T3>()
{
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<T2>() where T2 : List<T2>
{
ZZ<T2>(null);
ZZ2<object>(null);
#if CS80
static void Nop<T>(T data)
#else
void Nop<T>(T data)
#endif
{
}
#if CS80
static void ZZ<T3>(T3 t3) where T3 : T2
#else
void ZZ<T3>(T3 t3) where T3 : T2
#endif
{
Nop<List<T2>>(t3);
ZZ1<T3>(t3);
ZZ3();
void ZZ3()
{
Nop<List<T2>>(t3);
}
}
#if CS80
static void ZZ1<T3>(T3 t3)
#else
void ZZ1<T3>(T3 t3)
#endif
{
Nop<List<T2>>((List<T2>)(object)t3);
}
#if CS80
static void ZZ2<T3>(T3 t3)
#else
void ZZ2<T3>(T3 t3)
#endif
{
Nop<List<T2>>((List<T2>)(object)t3);
}
}
#if false
public void GenericArgsWithAnonymousType()
{
Method<int>();
#if CS80
static void Method<T2>()
#else
void Method<T2>()
#endif
{
int i = 0;
var obj2 = new {
A = 1
};
Method2(obj2);
Method3(obj2);
void Method2<T3>(T3 obj1)
{
//keep nested
i = 0;
}
#if CS80
static void Method3<T3>(T3 obj1)
#else
void Method3<T3>(T3 obj1)
#endif
{
}
}
}
#if CS80
public void NameConflict()
{
int i = 0;
Method<int>();
void Method<T2>()
{
Method();
void Method()
{
Method<T2>();
i = 0;
void Method<T2>()
{
i = 0;
Method();
static void Method()
{
}
}
}
}
}
#endif
#endif
}
private int field; private int field;
private Lazy<object> nonCapturinglocalFunctionInLambda = new Lazy<object>(delegate { private Lazy<object> nonCapturinglocalFunctionInLambda = new Lazy<object>(delegate {
return CreateValue(); return CreateValue();
#if CS80
static object CreateValue()
#else
object CreateValue() object CreateValue()
#endif
{ {
return null; return null;
} }
@ -69,7 +354,11 @@ namespace LocalFunctions
LocalWrite("Hello " + i); LocalWrite("Hello " + i);
} }
#if CS80
static void LocalWrite(string s)
#else
void LocalWrite(string s) void LocalWrite(string s)
#endif
{ {
Console.WriteLine(s); Console.WriteLine(s);
} }
@ -105,7 +394,11 @@ namespace LocalFunctions
LocalWrite("Hello " + i); LocalWrite("Hello " + i);
} }
#if CS80
static void LocalWrite(string s)
#else
void LocalWrite(string s) void LocalWrite(string s)
#endif
{ {
Console.WriteLine(s); Console.WriteLine(s);
} }
@ -166,7 +459,11 @@ namespace LocalFunctions
Test(5); Test(5);
LocalFunctions.Test(2); LocalFunctions.Test(2);
#if CS80
static void Test(int x)
#else
void Test(int x) void Test(int x)
#endif
{ {
Console.WriteLine("x: {0}", x); Console.WriteLine("x: {0}", x);
} }
@ -183,7 +480,11 @@ namespace LocalFunctions
Name(); Name();
action(); action();
#if CS80
static void Name()
#else
void Name() void Name()
#endif
{ {
} }
@ -194,12 +495,20 @@ namespace LocalFunctions
Use(Get(1), Get(2), Get(3)); Use(Get(1), Get(2), Get(3));
Use(Get(1), c: Get(2), b: Get(3)); Use(Get(1), c: Get(2), b: Get(3));
#if CS80
static int Get(int i)
#else
int Get(int i) int Get(int i)
#endif
{ {
return i; return i;
} }
#if CS80
static void Use(int a, int b, int c)
#else
void Use(int a, int b, int c) void Use(int a, int b, int c)
#endif
{ {
Console.WriteLine(a + b + c); Console.WriteLine(a + b + c);
} }
@ -232,7 +541,11 @@ namespace LocalFunctions
{ {
return FibHelper(i); return FibHelper(i);
#if CS80
static int FibHelper(int n)
#else
int FibHelper(int n) int FibHelper(int n)
#endif
{ {
if (n <= 0) { if (n <= 0) {
return 0; return 0;
@ -245,7 +558,11 @@ namespace LocalFunctions
{ {
return B(4) + C(3); return B(4) + C(3);
#if CS80
static int A(int i)
#else
int A(int i) int A(int i)
#endif
{ {
if (i > 0) { if (i > 0) {
return A(i - 1) + 2 * B(i - 1) + 3 * C(i - 1); return A(i - 1) + 2 * B(i - 1) + 3 * C(i - 1);
@ -253,7 +570,11 @@ namespace LocalFunctions
return 1; return 1;
} }
#if CS80
static int B(int i)
#else
int B(int i) int B(int i)
#endif
{ {
if (i > 0) { if (i > 0) {
return 3 * A(i - 1) + B(i - 1); return 3 * A(i - 1) + B(i - 1);
@ -261,7 +582,11 @@ namespace LocalFunctions
return 1; return 1;
} }
#if CS80
static int C(int i)
#else
int C(int i) int C(int i)
#endif
{ {
if (i > 0) { if (i > 0) {
return 2 * A(i - 1) + C(i - 1); return 2 * A(i - 1) + C(i - 1);
@ -332,5 +657,67 @@ namespace LocalFunctions
// } // }
// } // }
//} //}
public void NestedCapture1()
{
Method1(null);
#if CS80
static Action<object> Method1(Action<object> action)
#else
Action<object> Method1(Action<object> action)
#endif
{
return Method1_1;
void Method1_1(object containerBuilder)
{
Method1_2(containerBuilder);
}
void Method1_2(object containerBuilder)
{
action(containerBuilder);
}
}
}
public int NestedCapture2()
{
return Method();
#if CS80
static int Method()
#else
int Method()
#endif
{
int t0 = 0;
return ZZZ_0();
int ZZZ_0()
{
t0 = 0;
int t2 = t0;
return new Func<int>(ZZZ_0_0)();
int ZZZ_0_0()
{
t0 = 0;
t2 = 0;
return ZZZ_1();
}
}
int ZZZ_1()
{
t0 = 0;
int t = t0;
return new Func<int>(ZZZ_1_0)();
int ZZZ_1_0()
{
t0 = 0;
t = 0;
return 0;
}
}
}
}
} }
} }

40
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -194,9 +194,13 @@ namespace ICSharpCode.Decompiler.CSharp
TranslatedExpression target; TranslatedExpression target;
if (callOpCode == OpCode.NewObj) { if (callOpCode == OpCode.NewObj) {
target = default(TranslatedExpression); // no target target = default(TranslatedExpression); // no target
} else if (method.IsLocalFunction && localFunction != null) { } else if (localFunction != null) {
target = new IdentifierExpression(localFunction.Name) var ide = new IdentifierExpression(localFunction.Name);
.WithoutILInstruction() if (method.TypeArguments.Count > 0) {
int skipCount = localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters;
ide.TypeArguments.AddRange(method.TypeArguments.Skip(skipCount).Select(expressionBuilder.ConvertType));
}
target = ide.WithoutILInstruction()
.WithRR(ToMethodGroup(method, localFunction)); .WithRR(ToMethodGroup(method, localFunction));
} else { } else {
target = expressionBuilder.TranslateTarget( target = expressionBuilder.TranslateTarget(
@ -225,7 +229,7 @@ namespace ICSharpCode.Decompiler.CSharp
var argumentList = BuildArgumentList(expectedTargetDetails, target.ResolveResult, method, var argumentList = BuildArgumentList(expectedTargetDetails, target.ResolveResult, method,
firstParamIndex, callArguments, argumentToParameterMap); firstParamIndex, callArguments, argumentToParameterMap);
if (method.IsLocalFunction) { if (localFunction != null) {
return new InvocationExpression(target, argumentList.GetArgumentExpressions()) return new InvocationExpression(target, argumentList.GetArgumentExpressions())
.WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method, .WithRR(new CSharpInvocationResolveResult(target.ResolveResult, method,
argumentList.GetArgumentResolveResults().ToList(), isExpandedForm: argumentList.IsExpandedForm)); argumentList.GetArgumentResolveResults().ToList(), isExpandedForm: argumentList.IsExpandedForm));
@ -1304,10 +1308,14 @@ namespace ICSharpCode.Decompiler.CSharp
// 2. add type arguments (represented as bit 1) // 2. add type arguments (represented as bit 1)
// 3. cast target (represented as bit 2) // 3. cast target (represented as bit 2)
int step; int step;
ILFunction localFunction = null;
if (method.IsLocalFunction) { if (method.IsLocalFunction) {
step = 0; localFunction = expressionBuilder.ResolveLocalFunction(method);
Debug.Assert(localFunction != null);
}
if (localFunction != null) {
step = 2;
requireTarget = false; requireTarget = false;
var localFunction = expressionBuilder.ResolveLocalFunction(method);
result = ToMethodGroup(method, localFunction); result = ToMethodGroup(method, localFunction);
target = default; target = default;
targetType = default; targetType = default;
@ -1351,11 +1359,12 @@ namespace ICSharpCode.Decompiler.CSharp
memberDeclaringType: method.DeclaringType); memberDeclaringType: method.DeclaringType);
requireTarget = expressionBuilder.HidesVariableWithName(method.Name) requireTarget = expressionBuilder.HidesVariableWithName(method.Name)
|| (method.IsStatic ? !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition) : !(target.Expression is ThisReferenceExpression)); || (method.IsStatic ? !expressionBuilder.IsCurrentOrContainingType(method.DeclaringTypeDefinition) : !(target.Expression is ThisReferenceExpression));
step = requireTarget ? 1 : 0;
var savedTarget = target; var savedTarget = target;
for (step = requireTarget ? 1 : 0; step < 7; step++) { for (; step < 7; step++) {
ResolveResult targetResolveResult; 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; targetResolveResult = savedTarget.ResolveResult;
target = savedTarget; target = savedTarget;
} else { } else {
@ -1378,7 +1387,7 @@ namespace ICSharpCode.Decompiler.CSharp
break; break;
} }
} }
requireTarget = !method.IsLocalFunction && (step & 1) != 0; requireTarget = localFunction == null && (step & 1) != 0;
ExpressionWithResolveResult targetExpression; ExpressionWithResolveResult targetExpression;
Debug.Assert(result != null); Debug.Assert(result != null);
if (requireTarget) { if (requireTarget) {
@ -1389,8 +1398,13 @@ namespace ICSharpCode.Decompiler.CSharp
targetExpression = mre.WithRR(result); targetExpression = mre.WithRR(result);
} else { } else {
var ide = new IdentifierExpression(methodName); var ide = new IdentifierExpression(methodName);
if ((step & 2) != 0) if ((step & 2) != 0) {
ide.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType)); int skipCount = 0;
if (localFunction != null && method.TypeArguments.Count > 0) {
skipCount = localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters;
}
ide.TypeArguments.AddRange(method.TypeArguments.Skip(skipCount).Select(expressionBuilder.ConvertType));
}
targetExpression = ide.WithRR(result); targetExpression = ide.WithRR(result);
} }
return targetExpression; return targetExpression;
@ -1448,7 +1462,7 @@ namespace ICSharpCode.Decompiler.CSharp
method.DeclaringType, method.DeclaringType,
new IParameterizedMember[] { method } new IParameterizedMember[] { method }
) )
}, EmptyList<IType>.Instance }, method.TypeArguments.Skip(localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters).ToArray()
); );
} }

6
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -73,7 +73,7 @@ namespace ICSharpCode.Decompiler.CSharp
internal readonly ILFunction currentFunction; internal readonly ILFunction currentFunction;
internal readonly ICompilation compilation; internal readonly ICompilation compilation;
internal readonly CSharpResolver resolver; internal readonly CSharpResolver resolver;
readonly TypeSystemAstBuilder astBuilder; internal readonly TypeSystemAstBuilder astBuilder;
internal readonly TypeInference typeInference; internal readonly TypeInference typeInference;
internal readonly DecompilerSettings settings; internal readonly DecompilerSettings settings;
readonly CancellationToken cancellationToken; readonly CancellationToken cancellationToken;
@ -217,9 +217,9 @@ namespace ICSharpCode.Decompiler.CSharp
internal ILFunction ResolveLocalFunction(IMethod method) internal ILFunction ResolveLocalFunction(IMethod method)
{ {
Debug.Assert(method.IsLocalFunction); Debug.Assert(method.IsLocalFunction);
method = method.ReducedFrom; method = (IMethod)((IMethod)method.MemberDefinition).ReducedFrom.MemberDefinition;
foreach (var parent in currentFunction.Ancestors.OfType<ILFunction>()) { foreach (var parent in currentFunction.Ancestors.OfType<ILFunction>()) {
var definition = parent.LocalFunctions.FirstOrDefault(f => f.Method.Equals(method)); var definition = parent.LocalFunctions.FirstOrDefault(f => f.Method.MemberDefinition.Equals(method));
if (definition != null) { if (definition != null) {
return definition; return definition;
} }

13
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -992,9 +992,22 @@ namespace ICSharpCode.Decompiler.CSharp
stmt.Parameters.AddRange(exprBuilder.MakeParameters(function.Parameters, function)); stmt.Parameters.AddRange(exprBuilder.MakeParameters(function.Parameters, function));
stmt.ReturnType = exprBuilder.ConvertType(function.Method.ReturnType); stmt.ReturnType = exprBuilder.ConvertType(function.Method.ReturnType);
stmt.Body = nestedBuilder.ConvertAsBlock(function.Body); stmt.Body = nestedBuilder.ConvertAsBlock(function.Body);
if (function.Method.TypeParameters.Count > 0) {
var astBuilder = exprBuilder.astBuilder;
if (astBuilder.ShowTypeParameters) {
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));
}
}
}
if (function.IsAsync) { if (function.IsAsync) {
stmt.Modifiers |= Modifiers.Async; stmt.Modifiers |= Modifiers.Async;
} }
if (settings.StaticLocalFunctions && function.ReducedMethod.IsStaticLocalFunction) {
stmt.Modifiers |= Modifiers.Static;
}
stmt.AddAnnotation(new MemberResolveResult(null, function.ReducedMethod)); stmt.AddAnnotation(new MemberResolveResult(null, function.ReducedMethod));
return stmt; return stmt;
} }

4
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

@ -1807,7 +1807,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
#endregion #endregion
#region Convert Type Parameter #region Convert Type Parameter
TypeParameterDeclaration ConvertTypeParameter(ITypeParameter tp) internal TypeParameterDeclaration ConvertTypeParameter(ITypeParameter tp)
{ {
TypeParameterDeclaration decl = new TypeParameterDeclaration(); TypeParameterDeclaration decl = new TypeParameterDeclaration();
decl.Variance = tp.Variance; decl.Variance = tp.Variance;
@ -1817,7 +1817,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return decl; 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)) { if (!tp.HasDefaultConstructorConstraint && !tp.HasReferenceTypeConstraint && !tp.HasValueTypeConstraint && tp.NullabilityConstraint != Nullability.NotNullable && tp.DirectBaseTypes.All(IsObjectOrValueType)) {
return null; return null;

20
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -111,12 +111,13 @@ namespace ICSharpCode.Decompiler
readOnlyMethods = false; readOnlyMethods = false;
asyncUsingAndForEachStatement = false; asyncUsingAndForEachStatement = false;
asyncEnumerator = false; asyncEnumerator = false;
staticLocalFunctions = false;
} }
} }
public CSharp.LanguageVersion GetMinimumRequiredVersion() public CSharp.LanguageVersion GetMinimumRequiredVersion()
{ {
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement) if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions)
return CSharp.LanguageVersion.CSharp8_0; return CSharp.LanguageVersion.CSharp8_0;
if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers || patternBasedFixedStatement) if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers || patternBasedFixedStatement)
return CSharp.LanguageVersion.CSharp7_3; return CSharp.LanguageVersion.CSharp7_3;
@ -1082,6 +1083,23 @@ namespace ICSharpCode.Decompiler
} }
} }
bool staticLocalFunctions = true;
/// <summary>
/// Gets/Sets whether C# 8.0 static local functions should be transformed.
/// </summary>
[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; bool nullableReferenceTypes = true;
/// <summary> /// <summary>

1
ICSharpCode.Decompiler/IL/ILReader.cs

@ -100,7 +100,6 @@ namespace ICSharpCode.Decompiler.IL
this.method = this.method.Specialize(genericContext.ToSubstitution()); this.method = this.method.Specialize(genericContext.ToSubstitution());
} }
this.genericContext = genericContext; this.genericContext = genericContext;
var methodDefinition = metadata.GetMethodDefinition(methodDefinitionHandle);
this.body = body; this.body = body;
this.reader = body.GetILReader(); this.reader = body.GetILReader();
this.currentStack = ImmutableStack<ILVariable>.Empty; this.currentStack = ImmutableStack<ILVariable>.Empty;

2
ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs

@ -95,7 +95,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
} }
internal static GenericContext? GenericContextFromTypeArguments(TypeParameterSubstitution subst) static GenericContext? GenericContextFromTypeArguments(TypeParameterSubstitution subst)
{ {
var classTypeParameters = new List<ITypeParameter>(); var classTypeParameters = new List<ITypeParameter>();
var methodTypeParameters = new List<ITypeParameter>(); var methodTypeParameters = new List<ITypeParameter>();

198
ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs

@ -53,18 +53,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// ///
/// <para> /// <para>
/// local functions can either be used in method calls, i.e., call and callvirt instructions, /// 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., <c>newobj Delegate(&lt;target-expression&gt;, ldftn &lt;method&gt;)</c>. /// or can be used as part of the "delegate construction" pattern, i.e.,
/// <c>newobj Delegate(&lt;target-expression&gt;, ldftn &lt;method&gt;)</c>.
/// </para> /// </para>
/// 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. /// As local functions can be declared practically anywhere, we have to take a look at
/// After all use-sites are collected we construct the ILAst of the local function and add it to the parent function. /// all use-sites and infer the declaration location from that. Use-sites can be call,
/// Then all use-sites of the local-function are transformed to a call to the <c>LocalFunctionMethod</c> or a ldftn of the <c>LocalFunctionMethod</c>. /// 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
/// <c>LocalFunctionMethod</c> or a ldftn of the <c>LocalFunctionMethod</c>.
/// In a next step we handle all nested local functions. /// 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.
/// </summary> /// </summary>
public void Run(ILFunction function, ILTransformContext context) public void Run(ILFunction function, ILTransformContext context)
{ {
if (!context.Settings.LocalFunctions) if (!context.Settings.LocalFunctions)
return; 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; this.context = context;
this.resolveContext = new SimpleTypeResolveContext(function.Method); this.resolveContext = new SimpleTypeResolveContext(function.Method);
var localFunctions = new Dictionary<MethodDefinitionHandle, LocalFunctionInfo>(); var localFunctions = new Dictionary<MethodDefinitionHandle, LocalFunctionInfo>();
@ -78,25 +88,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms
continue; continue;
} }
var firstUseSite = info.UseSites[0];
context.StepStartGroup($"Transform " + info.Definition.Name, info.Definition); context.StepStartGroup($"Transform " + info.Definition.Name, info.Definition);
try { try {
var localFunction = info.Definition; var localFunction = info.Definition;
if (!localFunction.Method.IsStatic) { if (!localFunction.Method.IsStatic) {
var target = firstUseSite.Arguments[0];
context.Step($"Replace 'this' with {target}", localFunction);
var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); 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.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<ILFunction>().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);
}
}
}
context.Step($"Replace 'this' with {target}", localFunction);
localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar));
} }
foreach (var useSite in info.UseSites) { 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 {
DetermineCaptureAndDeclarationScope(localFunction, useSite); DetermineCaptureAndDeclarationScope(localFunction, useSite);
TransformToLocalFunctionInvocation(localFunction.ReducedMethod, useSite);
}
if (function.Method.IsConstructor && localFunction.DeclarationScope == null) { if (function.Method.IsConstructor && localFunction.DeclarationScope == null) {
localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite); localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite);
@ -109,12 +122,68 @@ namespace ICSharpCode.Decompiler.IL.Transforms
function.LocalFunctions.Remove(localFunction); function.LocalFunctions.Remove(localFunction);
declaringFunction.LocalFunctions.Add(localFunction); declaringFunction.LocalFunctions.Add(localFunction);
} }
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) {
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 { } finally {
context.StepEndGroup(); 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;
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<ILFunction>().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<MethodDefinitionHandle, LocalFunctionInfo> localFunctions) void FindUseSites(ILFunction function, ILTransformContext context, Dictionary<MethodDefinitionHandle, LocalFunctionInfo> localFunctions)
{ {
foreach (var inst in function.Body.Descendants) { foreach (var inst in function.Body.Descendants) {
@ -132,9 +201,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
context.StepStartGroup($"Read local function '{targetMethod.Name}'", inst); context.StepStartGroup($"Read local function '{targetMethod.Name}'", inst);
info = new LocalFunctionInfo() { info = new LocalFunctionInfo() {
UseSites = new List<CallInstruction>() { inst }, UseSites = new List<CallInstruction>() { inst },
Method = targetMethod, Method = (IMethod)targetMethod.MemberDefinition,
Definition = ReadLocalFunctionDefinition(context.Function, targetMethod)
}; };
var rootFunction = context.Function;
int skipCount = GetSkipCount(rootFunction, targetMethod);
info.Definition = ReadLocalFunctionDefinition(rootFunction, targetMethod, skipCount);
localFunctions.Add((MethodDefinitionHandle)targetMethod.MetadataToken, info); localFunctions.Add((MethodDefinitionHandle)targetMethod.MetadataToken, info);
if (info.Definition != null) { if (info.Definition != null) {
FindUseSites(info.Definition, context, localFunctions); FindUseSites(info.Definition, context, localFunctions);
@ -146,17 +217,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); var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken);
if (!methodDefinition.HasBody()) if (!methodDefinition.HasBody())
return null; return null;
var genericContext = DelegateConstruction.GenericContextFromTypeArguments(targetMethod.Substitution);
if (genericContext == null)
return null;
var ilReader = context.CreateILReader(); var ilReader = context.CreateILReader();
var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress);
var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, 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 // Embed the local function into the parent function's ILAst, so that "Show steps" can show
// how the local function body is being transformed. // how the local function body is being transformed.
rootFunction.LocalFunctions.Add(function); rootFunction.LocalFunctions.Add(function);
@ -165,10 +236,66 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var nestedContext = new ILTransformContext(context, function); var nestedContext = new ILTransformContext(context, function);
function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is LocalFunctionDecompiler)), nestedContext); function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is LocalFunctionDecompiler)), nestedContext);
function.DeclarationScope = null; function.DeclarationScope = null;
function.ReducedMethod = ReduceToLocalFunction(targetMethod); function.ReducedMethod = ReduceToLocalFunction(function.Method, skipCount);
return function; 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.TypeParameters.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<ITypeParameter>(targetMethod.DeclaringType.TypeParameters);
var methodTypeParameters = new List<ITypeParameter>(targetMethod.TypeParameters);
var skippedTypeArguments = targetMethod.DeclaringType.TypeArguments.Concat(targetMethod.TypeArguments).Take(total);
int idx = 0;
foreach (var skippedTA in skippedTypeArguments) {
int curIdx;
List<ITypeParameter> curParameters;
IReadOnlyList<IType> 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)skippedTA;
idx++;
}
if (idx != total) {
Debug.Assert(false);
return null;
}
return new TypeSystem.GenericContext(classTypeParameters, methodTypeParameters);
}
static T FindCommonAncestorInstruction<T>(ILInstruction a, ILInstruction b) static T FindCommonAncestorInstruction<T>(ILInstruction a, ILInstruction b)
where T : ILInstruction where T : ILInstruction
{ {
@ -204,7 +331,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return inst; return inst;
} }
LocalFunctionMethod ReduceToLocalFunction(IMethod method) LocalFunctionMethod ReduceToLocalFunction(IMethod method, int skipCount)
{ {
int parametersToRemove = 0; int parametersToRemove = 0;
for (int i = method.Parameters.Count - 1; i >= 0; i--) { for (int i = method.Parameters.Count - 1; i >= 0; i--) {
@ -212,21 +339,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms
break; break;
parametersToRemove++; parametersToRemove++;
} }
return new LocalFunctionMethod(method, parametersToRemove); return new LocalFunctionMethod(method, parametersToRemove, skipCount);
} }
static void TransformToLocalFunctionReference(ILFunction function, CallInstruction useSite) static void TransformToLocalFunctionReference(ILFunction function, CallInstruction useSite)
{ {
useSite.Arguments[0].ReplaceWith(new LdNull().WithILRange(useSite.Arguments[0])); useSite.Arguments[0].ReplaceWith(new LdNull().WithILRange(useSite.Arguments[0]));
var fnptr = (IInstructionWithMethodOperand)useSite.Arguments[1]; var fnptr = (IInstructionWithMethodOperand)useSite.Arguments[1];
var replacement = new LdFtn(function.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); useSite.Arguments[1].ReplaceWith(replacement);
} }
void TransformToLocalFunctionInvocation(LocalFunctionMethod reducedMethod, CallInstruction useSite) void TransformToLocalFunctionInvocation(LocalFunctionMethod reducedMethod, CallInstruction useSite)
{ {
var specializeMethod = reducedMethod.Specialize(useSite.Method.Substitution);
bool wasInstanceCall = !useSite.Method.IsStatic; bool wasInstanceCall = !useSite.Method.IsStatic;
var replacement = new Call(reducedMethod); var replacement = new Call(specializeMethod);
int firstArgumentIndex = wasInstanceCall ? 1 : 0; int firstArgumentIndex = wasInstanceCall ? 1 : 0;
int argumentCount = useSite.Arguments.Count; int argumentCount = useSite.Arguments.Count;
int reducedArgumentCount = argumentCount - (reducedMethod.NumberOfCompilerGeneratedParameters + firstArgumentIndex); int reducedArgumentCount = argumentCount - (reducedMethod.NumberOfCompilerGeneratedParameters + firstArgumentIndex);
@ -255,32 +384,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
int firstArgumentIndex = function.Method.IsStatic ? 0 : 1; int firstArgumentIndex = function.Method.IsStatic ? 0 : 1;
for (int i = useSite.Arguments.Count - 1; i >= firstArgumentIndex; i--) { 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; break;
} }
if (firstArgumentIndex > 0) { if (firstArgumentIndex > 0) {
HandleArgument(0, useSite.Arguments[0]); HandleArgument(function, firstArgumentIndex, 0, useSite.Arguments[0]);
}
} }
bool HandleArgument(int i, ILInstruction arg) bool HandleArgument(ILFunction function, int firstArgumentIndex, int i, ILInstruction arg)
{ {
ILVariable closureVar; ILVariable closureVar;
if (!(arg.MatchLdLoc(out closureVar) || arg.MatchLdLoca(out closureVar))) if (!(arg.MatchLdLoc(out closureVar) || arg.MatchLdLoca(out closureVar)))
return false; return false;
if (closureVar.Kind == VariableKind.NamedArgument) if (closureVar.Kind == VariableKind.NamedArgument)
return false; return false;
ITypeDefinition potentialDisplayClass = UnwrapByRef(closureVar.Type).GetDefinition(); if (!TransformDisplayClassUsage.IsClosure(context, closureVar, null, out _, out var initializer))
if (!TransformDisplayClassUsage.IsPotentialClosure(context, potentialDisplayClass))
return false; return false;
if (i - firstArgumentIndex >= 0) { if (i - firstArgumentIndex >= 0) {
Debug.Assert(i - firstArgumentIndex < function.Method.Parameters.Count && IsClosureParameter(function.Method.Parameters[i - firstArgumentIndex], resolveContext)); 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 // determine the capture scope of closureVar and the declaration scope of the function
var instructions = closureVar.StoreInstructions.OfType<ILInstruction>() var additionalScope = BlockContainer.FindClosestContainer(initializer);
.Concat(closureVar.AddressInstructions).OrderBy(inst => inst.StartILOffset).ToList();
var additionalScope = BlockContainer.FindClosestContainer(instructions.First());
if (closureVar.CaptureScope == null) if (closureVar.CaptureScope == null)
closureVar.CaptureScope = additionalScope; closureVar.CaptureScope = additionalScope;
else else
@ -291,7 +416,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
function.DeclarationScope = FindCommonAncestorInstruction<BlockContainer>(function.DeclarationScope, closureVar.CaptureScope); function.DeclarationScope = FindCommonAncestorInstruction<BlockContainer>(function.DeclarationScope, closureVar.CaptureScope);
return true; return true;
} }
}
bool IsInNestedLocalFunction(BlockContainer declarationScope, ILFunction function) bool IsInNestedLocalFunction(BlockContainer declarationScope, ILFunction function)
{ {

14
ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs

@ -59,7 +59,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
foreach (var v in f.Variables.ToArray()) { foreach (var v in f.Variables.ToArray()) {
if (context.Settings.YieldReturn && HandleMonoStateMachine(function, v, decompilationContext, f)) if (context.Settings.YieldReturn && HandleMonoStateMachine(function, v, decompilationContext, f))
continue; 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); 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)) { 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<ILInstruction> instructionsToRemove, out ITypeDefinition closureType, out ILInstruction initializer)
{ {
closureType = null; closureType = null;
initializer = null; initializer = null;
if (variable.IsSingleDefinition && variable.StoreInstructions.SingleOrDefault() is StLoc inst) { if (variable.IsSingleDefinition && variable.StoreInstructions.SingleOrDefault() is StLoc inst) {
initializer = inst; initializer = inst;
if (IsClosureInit(inst, out closureType)) { if (IsClosureInit(context, inst, out closureType)) {
instructionsToRemove.Add(inst); instructionsToRemove?.Add(inst);
return true; return true;
} }
} }
closureType = variable.Type.GetDefinition(); 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()); initializer = LocalFunctionDecompiler.GetStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First());
return true; return true;
} }
return false; 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) { if (inst.Value is NewObj newObj) {
closureType = newObj.Method.DeclaringTypeDefinition; closureType = newObj.Method.DeclaringTypeDefinition;
return closureType != null && IsPotentialClosure(this.context, newObj); return closureType != null && IsPotentialClosure(context, newObj);
} }
closureType = null; closureType = null;
return false; return false;

27
ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs

@ -18,6 +18,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection; using System.Reflection;
using ICSharpCode.Decompiler.Util; using ICSharpCode.Decompiler.Util;
@ -30,19 +31,22 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
{ {
readonly IMethod baseMethod; 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));
this.baseMethod = baseMethod; this.baseMethod = baseMethod;
this.NumberOfCompilerGeneratedParameters = numberOfCompilerGeneratedParameters; this.NumberOfCompilerGeneratedParameters = numberOfCompilerGeneratedParameters;
this.NumberOfCompilerGeneratedTypeParameters = numberOfCompilerGeneratedTypeParameters;
} }
public bool Equals(IMember obj, TypeVisitor typeNormalization) public bool Equals(IMember obj, TypeVisitor typeNormalization)
{ {
if (!(obj is LocalFunctionMethod other)) if (!(obj is LocalFunctionMethod other))
return false; return false;
return baseMethod.Equals(other.baseMethod, typeNormalization) return baseMethod.Equals(other.baseMethod, typeNormalization)
&& NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters; && NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters
&& NumberOfCompilerGeneratedTypeParameters == other.NumberOfCompilerGeneratedTypeParameters;
} }
public override bool Equals(object obj) public override bool Equals(object obj)
@ -50,23 +54,26 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
if (!(obj is LocalFunctionMethod other)) if (!(obj is LocalFunctionMethod other))
return false; return false;
return baseMethod.Equals(other.baseMethod) return baseMethod.Equals(other.baseMethod)
&& NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters; && NumberOfCompilerGeneratedParameters == other.NumberOfCompilerGeneratedParameters
&& NumberOfCompilerGeneratedTypeParameters == other.NumberOfCompilerGeneratedTypeParameters;
} }
public override int GetHashCode() public override int GetHashCode()
{ {
unchecked { return baseMethod.GetHashCode();
return baseMethod.GetHashCode() + NumberOfCompilerGeneratedParameters + 1;
}
} }
public override string ToString() public override string ToString()
{ {
return string.Format("[LocalFunctionMethod: ReducedFrom={0}, NumberOfGeneratedParameters={1}]", ReducedFrom, NumberOfCompilerGeneratedParameters); return string.Format("[LocalFunctionMethod: ReducedFrom={0}, NumberOfGeneratedParameters={1}, NumberOfCompilerGeneratedTypeParameters={2}]", ReducedFrom, NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedTypeParameters);
} }
internal int NumberOfCompilerGeneratedParameters { get; } internal int NumberOfCompilerGeneratedParameters { get; }
internal int NumberOfCompilerGeneratedTypeParameters { get; }
internal bool IsStaticLocalFunction => NumberOfCompilerGeneratedParameters == 0 && (baseMethod.IsStatic || (baseMethod.DeclaringTypeDefinition.IsCompilerGenerated() && !baseMethod.DeclaringType.GetFields(f => !f.IsStatic).Any()));
public IMember MemberDefinition => this; public IMember MemberDefinition => this;
public IType ReturnType => baseMethod.ReturnType; public IType ReturnType => baseMethod.ReturnType;
@ -79,7 +86,9 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
public IMethod Specialize(TypeParameterSubstitution substitution) public IMethod Specialize(TypeParameterSubstitution substitution)
{ {
return SpecializedMethod.Create(this, substitution); return new LocalFunctionMethod(
baseMethod.Specialize(substitution),
NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedTypeParameters);
} }
IMember IMember.Specialize(TypeParameterSubstitution substitution) IMember IMember.Specialize(TypeParameterSubstitution substitution)

9
ILSpy/Properties/Resources.Designer.cs generated

@ -835,6 +835,15 @@ namespace ICSharpCode.ILSpy.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Introduce static local functions.
/// </summary>
public static string DecompilerSettings_IntroduceStaticLocalFunctions {
get {
return ResourceManager.GetString("DecompilerSettings.IntroduceStaticLocalFunctions", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to IsByRefLikeAttribute should be replaced with &apos;ref&apos; modifiers on structs. /// Looks up a localized string similar to IsByRefLikeAttribute should be replaced with &apos;ref&apos; modifiers on structs.
/// </summary> /// </summary>

3
ILSpy/Properties/Resources.resx

@ -826,4 +826,7 @@ Are you sure you want to continue?</value>
<data name="ListsResetConfirmation" xml:space="preserve"> <data name="ListsResetConfirmation" xml:space="preserve">
<value>Are you sure that you want to remove all assembly lists and recreate the default assembly lists?</value> <value>Are you sure that you want to remove all assembly lists and recreate the default assembly lists?</value>
</data> </data>
<data name="DecompilerSettings.IntroduceStaticLocalFunctions" xml:space="preserve">
<value>Introduce static local functions</value>
</data>
</root> </root>
Loading…
Cancel
Save