From a231ab54d4a16e0db88e279b112fddb594a1368c Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 25 Jul 2020 21:02:43 +0200 Subject: [PATCH 1/6] Add test cases --- ICSharpCode.Decompiler.Tests/Helpers/Tester.cs | 3 +++ .../PrettyTestRunner.cs | 2 +- .../TestCases/Pretty/LocalFunctions.cs | 18 +++++++++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 1dc72a59f..f3c9536d0 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -257,6 +257,9 @@ namespace ICSharpCode.Decompiler.Tests.Helpers preprocessorSymbols.Add("LEGACY_CSC"); preprocessorSymbols.Add("LEGACY_VBC"); } + if (flags.HasFlag(CompilerOptions.Preview)) { + preprocessorSymbols.Add("CS90"); + } return preprocessorSymbols; } diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index c5687aa85..b657ad839 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -199,7 +199,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void LocalFunctions([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) { - RunForLibrary(cscOptions: cscOptions); + RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); } [Test] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 1f73795e2..10308cbbd 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -23,6 +23,12 @@ namespace LocalFunctions { internal class LocalFunctions { + [AttributeUsage(AttributeTargets.All)] + internal class MyAttribute : Attribute + { + + } + public class Generic where T1 : struct, ICloneable, IConvertible { public int MixedLocalFunction() where T2 : ICloneable, IConvertible @@ -31,15 +37,21 @@ namespace LocalFunctions object z = this; for (int j = 0; j < 10; j++) { int i = 0; - i += NonStaticMethod6(); - int NonStaticMethod6() + i += NonStaticMethod6(0); +#if CS90 + [My] + [return: My] + int NonStaticMethod6([My] int unused) +#else + int NonStaticMethod6(int unused) +#endif { t2 = default(T2); int l = 0; return NonStaticMethod6_1() + NonStaticMethod6_1() + z.GetHashCode(); int NonStaticMethod6_1() { - return i + l + NonStaticMethod6() + StaticMethod1(); + return i + l + NonStaticMethod6(0) + StaticMethod1(); } } } From 431bbaa489debf048809d155d6bb2f6b6333838d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 25 Jul 2020 21:05:16 +0200 Subject: [PATCH 2/6] RequiredNamespaceCollector: properly handle parts of methods --- .../CSharp/RequiredNamespaceCollector.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs index 878edf8fe..2aa5dd8f7 100644 --- a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs +++ b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs @@ -93,17 +93,18 @@ namespace ICSharpCode.Decompiler.CSharp CollectNamespacesForTypeReference(field.ReturnType); break; case IMethod method: - HandleAttributes(method.GetAttributes()); - HandleAttributes(method.GetReturnTypeAttributes()); - CollectNamespacesForTypeReference(method.ReturnType); - foreach (var param in method.Parameters) { - HandleAttributes(param.GetAttributes()); - CollectNamespacesForTypeReference(param.Type); - } - HandleTypeParameters(method.TypeParameters); var reader = module.PEFile.Reader; var parts = mappingInfo.GetMethodParts((MethodDefinitionHandle)method.MetadataToken).ToList(); foreach (var part in parts) { + var partMethod = module.ResolveMethod(part, genericContext); + HandleAttributes(partMethod.GetAttributes()); + HandleAttributes(partMethod.GetReturnTypeAttributes()); + CollectNamespacesForTypeReference(partMethod.ReturnType); + foreach (var param in partMethod.Parameters) { + HandleAttributes(param.GetAttributes()); + CollectNamespacesForTypeReference(param.Type); + } + HandleTypeParameters(partMethod.TypeParameters); HandleOverrides(part.GetMethodImplementations(module.metadata), module); var methodDef = module.metadata.GetMethodDefinition(part); if (method.HasBody) { From 18ace002662dfb203f415165a964ff2e1d10d0b7 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 25 Jul 2020 21:07:35 +0200 Subject: [PATCH 3/6] Refactor LocalFunctionDeclarationStatement + LocalFunctionMethod --- .../CSharp/CSharpDecompiler.cs | 41 +++++++----- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 12 +--- .../CSharp/ExpressionBuilder.cs | 6 +- .../OutputVisitor/CSharpOutputVisitor.cs | 14 +--- .../CSharp/StatementBuilder.cs | 29 ++------ .../LocalFunctionDeclarationStatement.cs | 67 ++----------------- .../CSharp/Syntax/TypeSystemAstBuilder.cs | 8 ++- .../Transforms/ContextTrackingVisitor.cs | 20 +++--- .../IL/Transforms/LocalFunctionDecompiler.cs | 2 +- .../Implementation/LocalFunctionMethod.cs | 31 +++++++-- 10 files changed, 85 insertions(+), 145 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 70358a8bc..0c18b39a3 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1345,30 +1345,35 @@ namespace ICSharpCode.Decompiler.CSharp } entityDecl.AddAnnotation(function); - if (function.IsIterator) { - if (localSettings.DecompileMemberBodies && !body.Descendants.Any(d => d is YieldReturnStatement || d is YieldBreakStatement)) { - body.Add(new YieldBreakStatement()); - } - if (function.IsAsync) { - RemoveAttribute(entityDecl, KnownAttribute.AsyncIteratorStateMachine); - } else { - RemoveAttribute(entityDecl, KnownAttribute.IteratorStateMachine); - } - if (function.StateMachineCompiledWithMono) { - RemoveAttribute(entityDecl, KnownAttribute.DebuggerHidden); - } + CleanUpMethodDeclaration(entityDecl, body, function, localSettings.DecompileMemberBodies); + } catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException)) { + throw new DecompilerException(module, method, innerException); + } + } + + internal static void CleanUpMethodDeclaration(EntityDeclaration entityDecl, BlockStatement body, ILFunction function, bool decompileBody = true) + { + if (function.IsIterator) { + if (decompileBody && !body.Descendants.Any(d => d is YieldReturnStatement || d is YieldBreakStatement)) { + body.Add(new YieldBreakStatement()); } if (function.IsAsync) { - entityDecl.Modifiers |= Modifiers.Async; - RemoveAttribute(entityDecl, KnownAttribute.AsyncStateMachine); - RemoveAttribute(entityDecl, KnownAttribute.DebuggerStepThrough); + RemoveAttribute(entityDecl, KnownAttribute.AsyncIteratorStateMachine); + } else { + RemoveAttribute(entityDecl, KnownAttribute.IteratorStateMachine); } - } catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException)) { - throw new DecompilerException(module, method, innerException); + if (function.StateMachineCompiledWithMono) { + RemoveAttribute(entityDecl, KnownAttribute.DebuggerHidden); + } + } + if (function.IsAsync) { + entityDecl.Modifiers |= Modifiers.Async; + RemoveAttribute(entityDecl, KnownAttribute.AsyncStateMachine); + RemoveAttribute(entityDecl, KnownAttribute.DebuggerStepThrough); } } - bool RemoveAttribute(EntityDeclaration entityDecl, KnownAttribute attributeType) + internal static bool RemoveAttribute(EntityDeclaration entityDecl, KnownAttribute attributeType) { bool found = false; foreach (var section in entityDecl.Attributes) { diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 9eeec424c..d1d633a0c 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -197,8 +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.NumberOfCompilerGeneratedTypeParameters; - ide.TypeArguments.AddRange(method.TypeArguments.Skip(skipCount).Select(expressionBuilder.ConvertType)); + ide.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType)); } ide.AddAnnotation(localFunction); target = ide.WithoutILInstruction() @@ -1327,7 +1326,6 @@ namespace ICSharpCode.Decompiler.CSharp target = default; targetType = default; methodName = localFunction.Name; - // TODO : think about how to handle generic local functions } else if (method.IsExtensionMethod && invokeMethod != null && method.Parameters.Count - 1 == invokeMethod.Parameters.Count) { step = 5; targetType = method.Parameters[0].Type; @@ -1406,11 +1404,7 @@ namespace ICSharpCode.Decompiler.CSharp } else { var ide = new IdentifierExpression(methodName); if ((step & 2) != 0) { - int skipCount = 0; - if (localFunction != null && method.TypeArguments.Count > 0) { - skipCount = localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters; - } - ide.TypeArguments.AddRange(method.TypeArguments.Skip(skipCount).Select(expressionBuilder.ConvertType)); + ide.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType)); } targetExpression = ide.WithRR(result); } @@ -1469,7 +1463,7 @@ namespace ICSharpCode.Decompiler.CSharp method.DeclaringType, new IParameterizedMember[] { method } ) - }, method.TypeArguments.Skip(localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters).ToArray() + }, method.TypeArguments ); } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index c0c583b00..140329dcd 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -92,6 +92,7 @@ namespace ICSharpCode.Decompiler.CSharp this.astBuilder = new TypeSystemAstBuilder(resolver); this.astBuilder.AlwaysUseShortTypeNames = true; this.astBuilder.AddResolveResultAnnotations = true; + this.astBuilder.ShowAttributes = true; this.astBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables; this.typeInference = new TypeInference(compilation) { Algorithm = TypeInferenceAlgorithm.Improved }; } @@ -1983,7 +1984,7 @@ namespace ICSharpCode.Decompiler.CSharp return SpecialType.UnknownType; } - internal IEnumerable MakeParameters(IReadOnlyList parameters, ILFunction function) + IEnumerable MakeParameters(IReadOnlyList parameters, ILFunction function) { var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index); int i = 0; @@ -1992,9 +1993,6 @@ namespace ICSharpCode.Decompiler.CSharp if (string.IsNullOrEmpty(pd.Name) && !pd.Type.IsArgList()) { // needs to be consistent with logic in ILReader.CreateILVarable(ParameterDefinition) pd.Name = "P_" + i; - // if this is a local function, we have to skip the parameters for closure references - if (settings.LocalFunctions && function.Kind == ILFunctionKind.LocalFunction && IL.Transforms.LocalFunctionDecompiler.IsClosureParameter(parameter, decompilationContext)) - break; } if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType()) pd.Type = null; diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index e9b3a90cd..81f3f0c6e 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1876,19 +1876,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor public virtual void VisitLocalFunctionDeclarationStatement(LocalFunctionDeclarationStatement localFunctionDeclarationStatement) { StartNode(localFunctionDeclarationStatement); - - WriteModifiers(localFunctionDeclarationStatement.ModifierTokens); - localFunctionDeclarationStatement.ReturnType.AcceptVisitor(this); - Space(); - WriteIdentifier(localFunctionDeclarationStatement.NameToken); - WriteTypeParameters(localFunctionDeclarationStatement.TypeParameters); - Space(policy.SpaceBeforeMethodDeclarationParentheses); - WriteCommaSeparatedListInParenthesis(localFunctionDeclarationStatement.Parameters, policy.SpaceWithinMethodDeclarationParentheses); - foreach (Constraint constraint in localFunctionDeclarationStatement.Constraints) { - constraint.AcceptVisitor(this); - } - WriteMethodBody(localFunctionDeclarationStatement.Body, policy.MethodBraceStyle); - + localFunctionDeclarationStatement.Declaration.AcceptVisitor(this); EndNode(localFunctionDeclarationStatement); } diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index c84b2850b..052ab9a59 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -1002,34 +1002,19 @@ namespace ICSharpCode.Decompiler.CSharp LocalFunctionDeclarationStatement TranslateFunction(ILFunction function) { - var stmt = new LocalFunctionDeclarationStatement(); var nestedBuilder = new StatementBuilder(typeSystem, exprBuilder.decompilationContext, function, settings, cancellationToken); - stmt.Name = function.Name; - stmt.Parameters.AddRange(exprBuilder.MakeParameters(function.Parameters, function)); - stmt.ReturnType = exprBuilder.ConvertType(function.Method.ReturnType); - stmt.Body = nestedBuilder.ConvertAsBlock(function.Body); + var astBuilder = exprBuilder.astBuilder; + var method = (MethodDeclaration)astBuilder.ConvertEntity(function.ReducedMethod); + method.Body = nestedBuilder.ConvertAsBlock(function.Body); Comment prev = null; foreach (string warning in function.Warnings) { - stmt.Body.InsertChildAfter(prev, prev = new Comment(warning), Roles.Comment); + method.Body.InsertChildAfter(prev, prev = new Comment(warning), Roles.Comment); } - 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) { - stmt.Modifiers |= Modifiers.Async; - } - if (settings.StaticLocalFunctions && function.ReducedMethod.IsStaticLocalFunction) { - stmt.Modifiers |= Modifiers.Static; - } + CSharpDecompiler.CleanUpMethodDeclaration(method, method.Body, function); + CSharpDecompiler.RemoveAttribute(method, KnownAttribute.CompilerGenerated); + var stmt = new LocalFunctionDeclarationStatement(method); stmt.AddAnnotation(new MemberResolveResult(null, function.ReducedMethod)); stmt.WithILInstruction(function); return stmt; diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs index 47ff1641c..105789bb3 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/LocalFunctionDeclarationStatement.cs @@ -16,69 +16,22 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.Collections.Generic; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; namespace ICSharpCode.Decompiler.CSharp.Syntax { public class LocalFunctionDeclarationStatement : Statement { - public AstNodeCollection TypeParameters { - get { return GetChildrenByRole(Roles.TypeParameter); } - } - - public CSharpTokenNode LParToken { - get { return GetChildByRole(Roles.LPar); } - } - - public AstNodeCollection Parameters { - get { return GetChildrenByRole(Roles.Parameter); } - } + public static readonly Role MethodDeclarationRole = new Role("Method"); - public CSharpTokenNode RParToken { - get { return GetChildByRole(Roles.RPar); } + public MethodDeclaration Declaration { + get { return GetChildByRole(MethodDeclarationRole); } + set { SetChildByRole(MethodDeclarationRole, value); } } - public AstNodeCollection Constraints { - get { return GetChildrenByRole(Roles.Constraint); } - } - - public BlockStatement Body { - get { return GetChildByRole(Roles.Body); } - set { SetChildByRole(Roles.Body, value); } - } - - public Modifiers Modifiers { - get { return EntityDeclaration.GetModifiers(this); } - set { EntityDeclaration.SetModifiers(this, value); } - } - - public bool HasModifier(Modifiers mod) + public LocalFunctionDeclarationStatement(MethodDeclaration methodDeclaration) { - return (Modifiers & mod) == mod; - } - - public IEnumerable ModifierTokens { - get { return GetChildrenByRole(EntityDeclaration.ModifierRole); } - } - - public virtual string Name { - get { - return GetChildByRole(Roles.Identifier).Name; - } - set { - SetChildByRole(Roles.Identifier, Identifier.Create(value, TextLocation.Empty)); - } - } - - public virtual Identifier NameToken { - get { return GetChildByRole(Roles.Identifier); } - set { SetChildByRole(Roles.Identifier, value); } - } - - public virtual AstType ReturnType { - get { return GetChildByRole(Roles.Type); } - set { SetChildByRole(Roles.Type, value); } + AddChild(methodDeclaration, MethodDeclarationRole); } public override void AcceptVisitor(IAstVisitor visitor) @@ -98,13 +51,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax protected internal override bool DoMatch(AstNode other, Match match) { - LocalFunctionDeclarationStatement o = other as LocalFunctionDeclarationStatement; - return o != null && MatchString(this.Name, o.Name) - && (this.Modifiers == Modifiers.Any || this.Modifiers == o.Modifiers) - && this.ReturnType.DoMatch(o.ReturnType, match) - && this.TypeParameters.DoMatch(o.TypeParameters, match) - && this.Parameters.DoMatch(o.Parameters, match) && this.Constraints.DoMatch(o.Constraints, match) - && this.Body.DoMatch(o.Body, match); + return other is LocalFunctionDeclarationStatement o && Declaration.DoMatch(o.Declaration, match); } } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 3fe6a5f02..33464c99a 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -1799,6 +1799,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return !member.IsStatic; case SymbolKind.Destructor: return false; + case SymbolKind.Method: + return !((IMethod)member).IsLocalFunction; default: return true; } @@ -1811,7 +1813,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax m |= ModifierFromAccessibility (member.Accessibility); } if (this.ShowModifiers) { - if (member.IsStatic) { + if (member is LocalFunctionMethod localFunction) { + if (localFunction.IsStaticLocalFunction) { + m |= Modifiers.Static; + } + } else if (member.IsStatic) { m |= Modifiers.Static; } else { var declaringType = member.DeclaringType; diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs index adf6e3f22..3f3015a33 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs @@ -55,56 +55,56 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public override TResult VisitMethodDeclaration(MethodDeclaration methodDeclaration) { - Debug.Assert(currentMethod == null); + var oldMethod = currentMethod; try { currentMethod = methodDeclaration.GetSymbol() as IMethod; return base.VisitMethodDeclaration(methodDeclaration); } finally { - currentMethod = null; + currentMethod = oldMethod; } } public override TResult VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration) { - Debug.Assert(currentMethod == null); + var oldMethod = currentMethod; try { currentMethod = constructorDeclaration.GetSymbol() as IMethod; return base.VisitConstructorDeclaration(constructorDeclaration); } finally { - currentMethod = null; + currentMethod = oldMethod; } } public override TResult VisitDestructorDeclaration(DestructorDeclaration destructorDeclaration) { - Debug.Assert(currentMethod == null); + var oldMethod = currentMethod; try { currentMethod = destructorDeclaration.GetSymbol() as IMethod; return base.VisitDestructorDeclaration(destructorDeclaration); } finally { - currentMethod = null; + currentMethod = oldMethod; } } public override TResult VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration) { - Debug.Assert(currentMethod == null); + var oldMethod = currentMethod; try { currentMethod = operatorDeclaration.GetSymbol() as IMethod; return base.VisitOperatorDeclaration(operatorDeclaration); } finally { - currentMethod = null; + currentMethod = oldMethod; } } public override TResult VisitAccessor(Accessor accessor) { - Debug.Assert(currentMethod == null); + var oldMethod = currentMethod; try { currentMethod = accessor.GetSymbol() as IMethod; return base.VisitAccessor(accessor); } finally { - currentMethod = null; + currentMethod = oldMethod; } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index de7e36c48..0b9ae651e 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -484,7 +484,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms break; parametersToRemove++; } - return new LocalFunctionMethod(method, parametersToRemove, typeParametersToRemove); + return new LocalFunctionMethod(method, method.Name, parametersToRemove, typeParametersToRemove); } static void TransformToLocalFunctionReference(ILFunction function, CallInstruction useSite) diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs index 242b6b193..57cce32dd 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/LocalFunctionMethod.cs @@ -31,11 +31,12 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation { readonly IMethod baseMethod; - public LocalFunctionMethod(IMethod baseMethod, int numberOfCompilerGeneratedParameters, int numberOfCompilerGeneratedTypeParameters) + public LocalFunctionMethod(IMethod baseMethod, string name, int numberOfCompilerGeneratedParameters, int numberOfCompilerGeneratedTypeParameters) { if (baseMethod == null) throw new ArgumentNullException(nameof(baseMethod)); this.baseMethod = baseMethod; + this.Name = name; this.NumberOfCompilerGeneratedParameters = numberOfCompilerGeneratedParameters; this.NumberOfCompilerGeneratedTypeParameters = numberOfCompilerGeneratedTypeParameters; } @@ -65,7 +66,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public override string ToString() { - return string.Format("[LocalFunctionMethod: ReducedFrom={0}, NumberOfGeneratedParameters={1}, NumberOfCompilerGeneratedTypeParameters={2}]", ReducedFrom, NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedTypeParameters); + return string.Format("[LocalFunctionMethod: ReducedFrom={0}, Name={1}, NumberOfGeneratedParameters={2}, NumberOfCompilerGeneratedTypeParameters={3}]", ReducedFrom, Name, NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedTypeParameters); } internal int NumberOfCompilerGeneratedParameters { get; } @@ -88,7 +89,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation { return new LocalFunctionMethod( baseMethod.Specialize(substitution), - NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedTypeParameters); + Name, NumberOfCompilerGeneratedParameters, NumberOfCompilerGeneratedTypeParameters); } IMember IMember.Specialize(TypeParameterSubstitution substitution) @@ -96,7 +97,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation return Specialize(substitution); } - public IReadOnlyList TypeParameters => baseMethod.TypeParameters; public bool IsExtensionMethod => baseMethod.IsExtensionMethod; public bool IsLocalFunction => true; public bool IsConstructor => baseMethod.IsConstructor; @@ -107,7 +107,24 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public IMember AccessorOwner => baseMethod.AccessorOwner; public MethodSemanticsAttributes AccessorKind => baseMethod.AccessorKind; public IMethod ReducedFrom => baseMethod; - public IReadOnlyList TypeArguments => baseMethod.TypeArguments; + + List typeParameters; + public IReadOnlyList TypeParameters { + get { + if (typeParameters == null) + typeParameters = new List(baseMethod.TypeParameters.Skip(NumberOfCompilerGeneratedTypeParameters)); + return typeParameters; + } + } + + List typeArguments; + public IReadOnlyList TypeArguments { + get { + if (typeArguments == null) + typeArguments = new List(baseMethod.TypeArguments.Skip(NumberOfCompilerGeneratedTypeParameters)); + return typeArguments; + } + } List parameters; public IReadOnlyList Parameters { @@ -137,8 +154,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public Accessibility Accessibility => baseMethod.Accessibility; - public string FullName => baseMethod.FullName; - public string Name => baseMethod.Name; + public string FullName => Name; + public string Name { get; set; } public string ReflectionName => baseMethod.ReflectionName; public string Namespace => baseMethod.Namespace; From acea95d0a153f96ebb8fbbcacfbc404038d3d223 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 25 Jul 2020 21:08:53 +0200 Subject: [PATCH 4/6] Properly rename LocalFunctionMethod.Name on all instructions --- .../IL/Transforms/AssignVariableNames.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index d95cc63da..86541e59a 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -20,6 +20,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Reflection.Metadata; + using Humanizer; using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.TypeSystem; @@ -51,6 +53,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms ILTransformContext context; string[] currentFieldNames; Dictionary reservedVariableNames; + Dictionary localFunctionMapping; HashSet loopCounters; const char maxLoopVariableName = 'n'; @@ -59,6 +62,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms this.context = context; currentFieldNames = function.Method.DeclaringTypeDefinition.Fields.Select(f => f.Name).ToArray(); reservedVariableNames = new Dictionary(); + localFunctionMapping = new Dictionary(); loopCounters = CollectLoopCounters(function); foreach (var f in function.Descendants.OfType()) { if (f.Method != null) { @@ -180,6 +184,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!LocalFunctionDecompiler.ParseLocalFunctionName(localFunction.Name, out _, out var newName) || !IsValidName(newName)) newName = null; localFunction.Name = newName; + localFunction.ReducedMethod.Name = newName; } // Now generate names: var mapping = new Dictionary(ILVariableEqualityComparer.Instance); @@ -199,6 +204,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms newName = GetAlternativeName("f"); } localFunction.Name = newName; + localFunction.ReducedMethod.Name = newName; + localFunctionMapping[(MethodDefinitionHandle)localFunction.ReducedMethod.MetadataToken] = newName; + } + foreach (var inst in function.Descendants) { + LocalFunctionMethod localFunction; + switch (inst) { + case Call call: + localFunction = call.Method as LocalFunctionMethod; + break; + case LdFtn ldftn: + localFunction = ldftn.Method as LocalFunctionMethod; + break; + default: + localFunction = null; + break; + } + if (localFunction == null || !localFunctionMapping.TryGetValue((MethodDefinitionHandle)localFunction.MetadataToken, out var name)) + continue; + localFunction.Name = name; } } From 67b2a45292f141b78b95f5753c32ed95ff483e63 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 25 Jul 2020 21:09:24 +0200 Subject: [PATCH 5/6] Improve tooltips and highlighting of local functions --- .../CSharp/OutputVisitor/CSharpAmbience.cs | 3 +- .../Output/TextTokenWriter.cs | 63 ++++++++++--------- ILSpy/Languages/CSharpLanguage.cs | 3 + 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs index c6b09f079..dab35cace 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs @@ -24,6 +24,7 @@ using System.Linq; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.Output; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.TypeSystem.Implementation; namespace ICSharpCode.Decompiler.CSharp.OutputVisitor { @@ -103,7 +104,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor if (symbol is ITypeDefinition) WriteTypeDeclarationName((ITypeDefinition)symbol, writer, formattingPolicy); - else if (symbol is IMember) + else if (symbol is IMember && !(symbol is LocalFunctionMethod)) WriteMemberDeclarationName((IMember)symbol, writer, formattingPolicy); else writer.WriteIdentifier(Identifier.Create(symbol.Name)); diff --git a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs index 1ca9a792a..82730d64f 100644 --- a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs +++ b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Linq; + using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.Resolver; @@ -26,6 +27,7 @@ using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.TypeSystem.Implementation; namespace ICSharpCode.Decompiler { @@ -39,7 +41,7 @@ namespace ICSharpCode.Decompiler bool inDocumentationComment = false; bool firstUsingDeclaration; bool lastUsingDeclaration; - + public TextTokenWriter(ITextOutput output, DecompilerSettings settings, IDecompilerTypeSystem typeSystem) { if (output == null) @@ -52,13 +54,13 @@ namespace ICSharpCode.Decompiler this.settings = settings; this.typeSystem = typeSystem; } - + public override void WriteIdentifier(Identifier identifier) { if (identifier.IsVerbatim || CSharpOutputVisitor.IsKeyword(identifier.Name, identifier)) { output.Write('@'); } - + var definition = GetCurrentDefinition(); string name = TextWriterTokenWriter.EscapeIdentifier(identifier.Name); switch (definition) { @@ -69,7 +71,7 @@ namespace ICSharpCode.Decompiler output.WriteReference(m, name, true); return; } - + var member = GetCurrentMemberReference(); switch (member) { case IType t: @@ -110,6 +112,7 @@ namespace ICSharpCode.Decompiler if (symbol != null && node.Role == Roles.Type && node.Parent is ObjectCreateExpression) { symbol = node.Parent.GetSymbol(); } + if (node is IdentifierExpression && node.Role == Roles.TargetExpression && node.Parent is InvocationExpression && symbol is IMember member) { var declaringType = member.DeclaringType; if (declaringType != null && declaringType.Kind == TypeKind.Delegate) @@ -123,10 +126,8 @@ namespace ICSharpCode.Decompiler if (symbol == null) return null; - //if (settings.AutomaticEvents && member is FieldDefinition) { - // var field = (FieldDefinition)member; - // return field.DeclaringType.Events.FirstOrDefault(ev => ev.Name == field.Name) ?? member; - //} + if (symbol is LocalFunctionMethod) + return null; return symbol; } @@ -142,14 +143,18 @@ namespace ICSharpCode.Decompiler if (letClauseVariable != null) return letClauseVariable; - var gotoStatement = node as GotoStatement; - if (gotoStatement != null) - { + if (node is GotoStatement gotoStatement) { var method = nodeStack.Select(nd => nd.GetSymbol() as IMethod).FirstOrDefault(mr => mr != null); if (method != null) return method + gotoStatement.Label; } + if (node.Role == Roles.TargetExpression && node.Parent is InvocationExpression) { + var symbol = node.Parent.GetSymbol(); + if (symbol is LocalFunctionMethod) + return symbol; + } + return null; } @@ -177,29 +182,29 @@ namespace ICSharpCode.Decompiler return method + label.Label; } - if (node is LocalFunctionDeclarationStatement) { - var localFunction = node.GetResolveResult() as MemberResolveResult; + if (node is MethodDeclaration && node.Parent is LocalFunctionDeclarationStatement) { + var localFunction = node.Parent.GetResolveResult() as MemberResolveResult; if (localFunction != null) return localFunction.Member; } return null; } - + ISymbol GetCurrentDefinition() { if (nodeStack == null || nodeStack.Count == 0) return null; - + var node = nodeStack.Peek(); if (node is Identifier) node = node.Parent; if (IsDefinition(ref node)) return node.GetSymbol(); - + return null; } - + public override void WriteKeyword(Role role, string keyword) { //To make reference for 'this' and 'base' keywords in the ClassName():this() expression @@ -211,7 +216,7 @@ namespace ICSharpCode.Decompiler } output.Write(keyword); } - + public override void WriteToken(Role role, string token) { switch (token) { @@ -253,22 +258,22 @@ namespace ICSharpCode.Decompiler break; } } - + public override void Space() { output.Write(' '); } - + public override void Indent() { output.Indent(); } - + public override void Unindent() { output.Unindent(); } - + public override void NewLine() { if (!firstUsingDeclaration && lastUsingDeclaration) { @@ -277,7 +282,7 @@ namespace ICSharpCode.Decompiler } output.WriteLine(); } - + public override void WriteComment(CommentType commentType, string content) { switch (commentType) { @@ -309,7 +314,7 @@ namespace ICSharpCode.Decompiler break; } } - + public override void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) { // pre-processor directive must start on its own line @@ -321,7 +326,7 @@ namespace ICSharpCode.Decompiler } output.WriteLine(); } - + public override void WritePrimitiveValue(object value, LiteralFormat format = LiteralFormat.None) { new TextWriterTokenWriter(new TextOutputWriter(output)).WritePrimitiveValue(value, format); @@ -376,7 +381,7 @@ namespace ICSharpCode.Decompiler break; } } - + public override void StartNode(AstNode node) { if (nodeStack.Count == 0) { @@ -390,7 +395,7 @@ namespace ICSharpCode.Decompiler } nodeStack.Push(node); } - + private bool IsUsingDeclaration(AstNode node) { return node is UsingDeclaration || node is UsingAliasDeclaration; @@ -401,10 +406,10 @@ namespace ICSharpCode.Decompiler if (nodeStack.Pop() != node) throw new InvalidOperationException(); } - + public static bool IsDefinition(ref AstNode node) { - if (node is EntityDeclaration) + if (node is EntityDeclaration && !(node.Parent is LocalFunctionDeclarationStatement)) return true; if (node is VariableInitializer && node.Parent is FieldDeclaration) { node = node.Parent; diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index 8b0253026..96c22ce91 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -648,6 +648,9 @@ namespace ICSharpCode.ILSpy if (!settings.LiftNullables) { flags &= ~ConversionFlags.UseNullableSpecifierForValueTypes; } + if (entity is IMethod m && m.IsLocalFunction) { + writer.WriteIdentifier(Identifier.Create("(local)")); + } new CSharpAmbience() { ConversionFlags = flags, }.ConvertSymbol(entity, writer, settings.CSharpFormattingOptions); From 33bc9fbef47e70f8fb945c1c53a550cf12e74c49 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 26 Jul 2020 10:17:23 +0200 Subject: [PATCH 6/6] Fix display of generic type parameters of local functions in tooltips --- .../TestCases/Pretty/LocalFunctions.cs | 2 +- ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 10308cbbd..c38a81c17 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -41,7 +41,7 @@ namespace LocalFunctions #if CS90 [My] [return: My] - int NonStaticMethod6([My] int unused) + int NonStaticMethod6<[My] T3>([My] int unused) #else int NonStaticMethod6(int unused) #endif diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs index dab35cace..71506af61 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs @@ -104,7 +104,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor if (symbol is ITypeDefinition) WriteTypeDeclarationName((ITypeDefinition)symbol, writer, formattingPolicy); - else if (symbol is IMember && !(symbol is LocalFunctionMethod)) + else if (symbol is IMember) WriteMemberDeclarationName((IMember)symbol, writer, formattingPolicy); else writer.WriteIdentifier(Identifier.Create(symbol.Name)); @@ -223,7 +223,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor { TypeSystemAstBuilder astBuilder = CreateAstBuilder(); EntityDeclaration node = astBuilder.ConvertEntity(member); - if ((ConversionFlags & ConversionFlags.ShowDeclaringType) == ConversionFlags.ShowDeclaringType && member.DeclaringType != null) { + if ((ConversionFlags & ConversionFlags.ShowDeclaringType) == ConversionFlags.ShowDeclaringType && member.DeclaringType != null && !(member is LocalFunctionMethod)) { ConvertType(member.DeclaringType, writer, formattingPolicy); writer.WriteToken(Roles.Dot, "."); }