diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 0f100b873..ae7fa8328 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -33,11 +33,11 @@ - TRACE;DEBUG;NET46;ROSLYN;CS60;CS70;CS71;CS72;CS73 + TRACE;DEBUG;ROSLYN;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100 - TRACE;NET46;ROSLYN;CS60;CS70;CS71;CS72;CS73 + TRACE;ROSLYN;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100 diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs index ac50c619a..9b5123e8d 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs @@ -20,8 +20,11 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +#if CS100 +using System.Threading.Tasks; +#endif -namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction { public static class DelegateConstruction { @@ -527,6 +530,46 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return (int _, int _, int _) => 0; } #endif + +#if CS100 + public static Func LambdaWithAttribute0() + { + return [My] () => 0; + } + + public static Func LambdaWithAttribute1() + { + return [My] (int x) => 0; + } + + public static Func LambdaWithAttributeOnParam() + { + return ([My] int x) => 0; + } + + public static Func> AsyncLambdaWithAttribute0() + { + return [My] async () => 0; + } + public static Action StatementLambdaWithAttribute0() + { + return [My] () => { + }; + } + + public static Action StatementLambdaWithAttribute1() + { + return [return: My] (int x) => { + Console.WriteLine(x); + }; + } + public static Action StatementLambdaWithAttribute2() + { + return ([My] int x) => { + Console.WriteLine(x); + }; + } +#endif } public class Issue1867 @@ -551,4 +594,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return () => m1.value + 1 == 4 && m2.value > 5; } } + + [AttributeUsage(AttributeTargets.All)] + internal class MyAttribute : Attribute + { + } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InterfaceTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InterfaceTests.cs index d80623929..7c070ed58 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InterfaceTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InterfaceTests.cs @@ -39,7 +39,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { } - + void DefaultMethod() { Method(); diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 977536856..c354df411 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -2302,6 +2302,24 @@ namespace ICSharpCode.Decompiler.CSharp { body.InsertChildAfter(prev, prev = new Comment(warning), Roles.Comment); } + var attributeSections = new List(); + foreach (var attr in method?.GetAttributes() ?? Enumerable.Empty()) + { + if (attr.AttributeType.IsKnownType(KnownAttribute.CompilerGenerated)) + continue; + if (function.IsAsync) + { + if (attr.AttributeType.IsKnownType(KnownAttribute.AsyncStateMachine)) + continue; + if (attr.AttributeType.IsKnownType(KnownAttribute.DebuggerStepThrough)) + continue; + } + attributeSections.Add(new AttributeSection(astBuilder.ConvertAttribute(attr))); + } + foreach (var attr in method?.GetReturnTypeAttributes() ?? Enumerable.Empty()) + { + attributeSections.Add(new AttributeSection(astBuilder.ConvertAttribute(attr)) { AttributeTarget = "return" }); + } bool isLambda = false; if (ame.Parameters.Any(p => p.Type.IsNull)) @@ -2309,6 +2327,11 @@ namespace ICSharpCode.Decompiler.CSharp // if there is an anonymous type involved, we are forced to use a lambda expression. isLambda = true; } + else if (attributeSections.Count > 0 || ame.Parameters.Any(p => p.Attributes.Any())) + { + // C# 10 lambdas can have attributes, but anonymous methods cannot + isLambda = true; + } else if (settings.UseLambdaSyntax && ame.Parameters.All(p => p.ParameterModifier == ParameterModifier.None)) { // otherwise use lambda only if an expression lambda is possible @@ -2331,6 +2354,7 @@ namespace ICSharpCode.Decompiler.CSharp if (isLambda) { LambdaExpression lambda = new LambdaExpression(); + lambda.Attributes.AddRange(attributeSections); lambda.IsAsync = ame.IsAsync; lambda.CopyAnnotationsFrom(ame); ame.Parameters.MoveTo(lambda.Parameters); diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index a38e56842..4b7bcd93f 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1007,6 +1007,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor public virtual void VisitLambdaExpression(LambdaExpression lambdaExpression) { StartNode(lambdaExpression); + WriteAttributes(lambdaExpression.Attributes); if (lambdaExpression.IsAsync) { WriteKeyword(LambdaExpression.AsyncModifierRole); @@ -1502,6 +1503,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor break; case TypeParameterDeclaration _: case ComposedType _: + case LambdaExpression _: Space(); break; default: diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/LambdaExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/LambdaExpression.cs index c3829d882..8782aa1d8 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/LambdaExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/LambdaExpression.cs @@ -31,11 +31,16 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax /// public class LambdaExpression : Expression { + public static readonly Role AttributeRole = new Role("Attribute", null); public readonly static TokenRole AsyncModifierRole = new TokenRole("async"); public static readonly Role BodyRole = new Role("Body", AstNode.Null); bool isAsync; + public AstNodeCollection Attributes { + get { return base.GetChildrenByRole(AttributeRole); } + } + public bool IsAsync { get { return isAsync; } set { ThrowIfFrozen(); isAsync = value; }