diff --git a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
index 364180edc..ad3399bf9 100644
--- a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
@@ -283,6 +283,12 @@ namespace ICSharpCode.Decompiler.Tests
RunCS(options: options);
}
+ [Test]
+ public void LocalFunctions([ValueSource(nameof(roslynOnlyOptions))] CSharpCompilerOptions options)
+ {
+ RunCS(options: options);
+ }
+
void RunCS([CallerMemberName] string testName = null, CSharpCompilerOptions options = CSharpCompilerOptions.UseDebug)
{
string testFileName = testName + ".cs";
diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index 3852f4955..ed47fa69c 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -68,6 +68,7 @@
+
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/LocalFunctions.cs
new file mode 100644
index 000000000..f05d95f27
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/LocalFunctions.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LocalFunctions
+{
+ class LocalFunctions
+ {
+ int field;
+
+ public static void Main(string[] args)
+ {
+ StaticContextNoCapture(10);
+ StaticContextSimpleCapture(10);
+ StaticContextCaptureInForLoop(10);
+ var inst = new LocalFunctions() { field = 10 };
+ inst.ContextNoCapture();
+ inst.ContextSimpleCapture();
+ inst.ContextCaptureInForLoop();
+ }
+
+ public static void StaticContextNoCapture(int length)
+ {
+ for (int i = 0; i < length; i++) {
+ LocalWrite("Hello " + i);
+ }
+
+ void LocalWrite(string s) => Console.WriteLine(s);
+ }
+
+ public static void StaticContextSimpleCapture(int length)
+ {
+ for (int i = 0; i < length; i++) {
+ LocalWrite();
+ }
+
+ void LocalWrite() => Console.WriteLine("Hello " + length);
+ }
+
+ public static void StaticContextCaptureInForLoop(int length)
+ {
+ for (int i = 0; i < length; i++) {
+ void LocalWrite() => Console.WriteLine("Hello " + i + "/" + length);
+ LocalWrite();
+ }
+ }
+
+ public void ContextNoCapture()
+ {
+ for (int i = 0; i < field; i++) {
+ LocalWrite("Hello " + i);
+ }
+
+ void LocalWrite(string s) => Console.WriteLine(s);
+ }
+
+ public void ContextSimpleCapture()
+ {
+ for (int i = 0; i < field; i++) {
+ LocalWrite();
+ }
+
+ void LocalWrite() => Console.WriteLine("Hello " + field);
+ }
+
+ public void ContextCaptureInForLoop()
+ {
+ for (int i = 0; i < field; i++) {
+ void LocalWrite() => Console.WriteLine("Hello " + i + "/" + field);
+ LocalWrite();
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 7a5d0acfe..8e8a3f127 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -249,9 +249,11 @@ namespace ICSharpCode.Decompiler.CSharp
var methodSemantics = module.MethodSemanticsLookup.GetSemantics(methodHandle).Item2;
if (methodSemantics != 0 && methodSemantics != System.Reflection.MethodSemanticsAttributes.Other)
return true;
+ if (LocalFunctionDecompiler.IsLocalFunctionMethod(module, methodHandle))
+ return settings.LocalFunctions;
if (settings.AnonymousMethods && methodHandle.HasGeneratedName(metadata) && methodHandle.IsCompilerGenerated(metadata))
return true;
- if (settings.AsyncAwait && AsyncAwaitDecompiler.IsCompilerGeneratedMainMethod(module, (MethodDefinitionHandle)member))
+ if (settings.AsyncAwait && AsyncAwaitDecompiler.IsCompilerGeneratedMainMethod(module, methodHandle))
return true;
return false;
case HandleKind.TypeDefinition:
@@ -259,6 +261,8 @@ namespace ICSharpCode.Decompiler.CSharp
var type = metadata.GetTypeDefinition(typeHandle);
name = metadata.GetString(type.Name);
if (!type.GetDeclaringType().IsNil) {
+ if (LocalFunctionDecompiler.IsLocalFunctionDisplayClass(module, typeHandle))
+ return settings.LocalFunctions;
if (settings.AnonymousMethods && IsClosureType(type, metadata))
return true;
if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(typeHandle, metadata))
@@ -1070,6 +1074,14 @@ namespace ICSharpCode.Decompiler.CSharp
}
FixParameterNames(methodDecl);
var methodDefinition = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken);
+ if (!settings.LocalFunctions && LocalFunctionDecompiler.IsLocalFunctionMethod(method.ParentModule.PEFile, (MethodDefinitionHandle)method.MetadataToken)) {
+ // if local functions are not active and we're dealing with a local function,
+ // reduce the visibility of the method to private,
+ // otherwise this leads to compile errors because the display classes have lesser accessibility.
+ // Note: removing and then adding the static modifier again is necessary to set the private modifier before all other modifiers.
+ methodDecl.Modifiers &= ~(Modifiers.Internal | Modifiers.Static);
+ methodDecl.Modifiers |= Modifiers.Private | (method.IsStatic ? Modifiers.Static : 0);
+ }
if (methodDefinition.HasBody()) {
DecompileBody(method, methodDecl, decompileRun, decompilationContext);
} else if (!method.IsAbstract && method.DeclaringType.Kind != TypeKind.Interface) {
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index 39c24387f..d7a19d80c 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -81,6 +81,7 @@ namespace ICSharpCode.Decompiler
tupleTypes = false;
tupleConversions = false;
discards = false;
+ localFunctions = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp7_2) {
introduceReadonlyAndInModifiers = false;
@@ -100,7 +101,7 @@ namespace ICSharpCode.Decompiler
if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers || nonTrailingNamedArguments)
return CSharp.LanguageVersion.CSharp7_2;
// C# 7.1 missing
- if (outVariables || tupleTypes || tupleConversions || discards)
+ if (outVariables || tupleTypes || tupleConversions || discards || localFunctions)
return CSharp.LanguageVersion.CSharp7;
if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation
|| stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers)
@@ -774,6 +775,23 @@ namespace ICSharpCode.Decompiler
}
}
+ bool localFunctions = false;
+
+ ///
+ /// Gets/Sets whether C# 7.0 local functions should be used.
+ /// Note: this language feature is currenly not implemented and this setting is always false.
+ ///
+ public bool LocalFunctions {
+ get { return localFunctions; }
+ set {
+ if (localFunctions != value) {
+ throw new NotImplementedException("C# 7.0 local functions are not implemented!");
+ //localFunctions = value;
+ //OnPropertyChanged();
+ }
+ }
+ }
+
#region Options to aid VB decompilation
bool assumeArrayLengthFitsIntoInt32 = true;
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index b85178a10..1c57853f4 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -267,6 +267,7 @@
+
diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs
new file mode 100644
index 000000000..2b8304815
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Text;
+using System.Text.RegularExpressions;
+using ICSharpCode.Decompiler.Metadata;
+using ICSharpCode.Decompiler.Util;
+
+namespace ICSharpCode.Decompiler.IL.Transforms
+{
+ class LocalFunctionDecompiler : IILTransform
+ {
+ public void Run(ILFunction function, ILTransformContext context)
+ {
+ throw new NotImplementedException();
+ }
+
+ public static bool IsLocalFunctionMethod(PEFile module, MethodDefinitionHandle methodHandle)
+ {
+ var metadata = module.Metadata;
+ var method = metadata.GetMethodDefinition(methodHandle);
+
+ if ((method.Attributes & MethodAttributes.Assembly) == 0 || !method.IsCompilerGenerated(metadata))
+ return false;
+
+ if (!ParseLocalFunctionName(metadata.GetString(method.Name), out _, out _))
+ return false;
+
+ return true;
+ }
+
+ public static bool IsLocalFunctionDisplayClass(PEFile module, TypeDefinitionHandle typeHandle)
+ {
+ var metadata = module.Metadata;
+ var type = metadata.GetTypeDefinition(typeHandle);
+
+ if ((type.Attributes & TypeAttributes.NestedPrivate) == 0)
+ return false;
+ if (!type.HasGeneratedName(metadata))
+ return false;
+
+ var declaringTypeHandle = type.GetDeclaringType();
+ var declaringType = metadata.GetTypeDefinition(declaringTypeHandle);
+
+ foreach (var method in declaringType.GetMethods()) {
+ if (!IsLocalFunctionMethod(module, method))
+ continue;
+ var md = metadata.GetMethodDefinition(method);
+ if (md.DecodeSignature(new FindTypeDecoder(typeHandle), default).ParameterTypes.Any())
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Newer Roslyn versions use the format "<callerName>g__functionName|x_y"
+ /// Older versions use "<callerName>g__functionNamex_y"
+ ///
+ static readonly Regex functionNameRegex = new Regex(@"^<(.*)>g__(.*)\|{0,1}\d+_\d+$", RegexOptions.Compiled);
+
+ static bool ParseLocalFunctionName(string name, out string callerName, out string functionName)
+ {
+ callerName = null;
+ functionName = null;
+ if (string.IsNullOrWhiteSpace(name))
+ return false;
+ var match = functionNameRegex.Match(name);
+ callerName = match.Groups[1].Value;
+ functionName = match.Groups[2].Value;
+ return match.Success;
+ }
+
+ struct FindTypeDecoder : ISignatureTypeProvider
+ {
+ TypeDefinitionHandle handle;
+
+ public FindTypeDecoder(TypeDefinitionHandle handle)
+ {
+ this.handle = handle;
+ }
+
+ public bool GetArrayType(bool elementType, ArrayShape shape) => elementType;
+ public bool GetByReferenceType(bool elementType) => elementType;
+ public bool GetFunctionPointerType(MethodSignature signature) => false;
+ public bool GetGenericInstantiation(bool genericType, ImmutableArray typeArguments) => genericType;
+ public bool GetGenericMethodParameter(Unit genericContext, int index) => false;
+ public bool GetGenericTypeParameter(Unit genericContext, int index) => false;
+ public bool GetModifiedType(bool modifier, bool unmodifiedType, bool isRequired) => unmodifiedType;
+ public bool GetPinnedType(bool elementType) => elementType;
+ public bool GetPointerType(bool elementType) => elementType;
+ public bool GetPrimitiveType(PrimitiveTypeCode typeCode) => false;
+ public bool GetSZArrayType(bool elementType) => false;
+
+ public bool GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
+ {
+ return this.handle == handle;
+ }
+
+ public bool GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
+ {
+ return false;
+ }
+
+ public bool GetTypeFromSpecification(MetadataReader reader, Unit genericContext, TypeSpecificationHandle handle, byte rawTypeKind)
+ {
+ return reader.GetTypeSpecification(handle).DecodeSignature(this, genericContext);
+ }
+ }
+ }
+}