diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index bdea4570f..ed9e2739f 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection.PortableExecutable; @@ -330,6 +329,45 @@ namespace ICSharpCode.Decompiler.Tests.Helpers return tempFile; } + const string nonEmbeddedAttributesSnippet = @" +using System; + +#if !NET60 +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class CompilerFeatureRequiredAttribute : Attribute + { + public CompilerFeatureRequiredAttribute(string featureName) + { + } + } + + internal class IsExternalInit + { + } +#endif +#if !NET70 + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] + internal sealed class RequiredMemberAttribute : Attribute + { + } +#endif +#if !NET60 +} +#endif +"; + + static readonly Lazy nonEmbeddedAttributesSnippetFile = new Lazy(GetNonEmbeddedAttributesSnippetFile); + + static string GetNonEmbeddedAttributesSnippetFile() + { + // Note: this leaks a temporary file, we're not attempting to delete it, because it is only one. + var tempFile = Path.GetTempFileName(); + File.WriteAllText(tempFile, nonEmbeddedAttributesSnippet); + return tempFile; + } + public static List GetPreprocessorSymbols(CompilerOptions flags) { var preprocessorSymbols = new List(); @@ -419,6 +457,11 @@ namespace ICSharpCode.Decompiler.Tests.Helpers sourceFileNames.Add(targetFrameworkAttributeSnippetFile.Value); } + if (targetNet40) + { + sourceFileNames.Add(nonEmbeddedAttributesSnippetFile.Value); + } + var preprocessorSymbols = GetPreprocessorSymbols(flags); if ((flags & CompilerOptions.UseMcsMask) == 0) diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 305d22d96..0ec42ecbe 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -151,7 +151,7 @@ - + diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs index 49a4fba3e..f6937aa02 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs @@ -44,7 +44,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public record PairWithPrimaryCtor(A First, B Second); public record PrimaryCtor(int A, string B); - public record PrimaryCtorWithAttribute([RecordTest("param")] [property: RecordTest("property")][field: RecordTest("field")] int a); + public record PrimaryCtorWithAttribute([RecordTest("param")][property: RecordTest("property")][field: RecordTest("field")] int a); public record PrimaryCtorWithField(int A, string B) { public double C = 1.0; @@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public record struct PairWithPrimaryCtor(A First, B Second); public record struct PrimaryCtor(int A, string B); - public record struct PrimaryCtorWithAttribute([RecordTest("param")] [property: RecordTest("property")][field: RecordTest("field")] int a); + public record struct PrimaryCtorWithAttribute([RecordTest("param")][property: RecordTest("property")][field: RecordTest("field")] int a); public record struct PrimaryCtorWithField(int A, string B) { public double C = 1.0; @@ -242,27 +242,3 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } #endif } -#if !NET60 -namespace System.Runtime.CompilerServices -{ - [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class CompilerFeatureRequiredAttribute : Attribute - { - public CompilerFeatureRequiredAttribute(string featureName) - { - } - } - - internal class IsExternalInit - { - } -#endif -#if !NET70 - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] - internal sealed class RequiredMemberAttribute : Attribute - { - } -#endif -#if !NET60 -} -#endif diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs index c796990eb..1e90c8382 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Structs.cs @@ -59,20 +59,4 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public required string LastName { get; set; } } #endif -} - -namespace System.Runtime.CompilerServices -{ - [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class CompilerFeatureRequiredAttribute : Attribute - { - public CompilerFeatureRequiredAttribute(string featureName) - { - } - } - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] - internal sealed class RequiredMemberAttribute : Attribute - { - } -} +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 96d591dab..a462ccb9f 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1530,8 +1530,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor WriteToken(Roles.RBracket); switch (attributeSection.Parent) { - case ParameterDeclaration _: - if (attributeSection.NextSibling is AttributeSection) + case ParameterDeclaration pd: + if (pd.Attributes.Last() != attributeSection) Space(policy.SpaceBetweenParameterAttributeSections); else Space(); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs index 828d220af..907fb807f 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs @@ -172,13 +172,27 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms "Microsoft.CodeAnalysis.EmbeddedAttribute", }; + internal static readonly HashSet nonEmbeddedAttributeNames = new HashSet() { + // non-embedded attributes, but we still want to remove them + "System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute", + "System.Runtime.CompilerServices.RequiredMemberAttribute", + "System.Runtime.CompilerServices.IsExternalInit", + }; + public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration) { var typeDefinition = typeDeclaration.GetSymbol() as ITypeDefinition; - if (typeDefinition == null || !attributeNames.Contains(typeDefinition.FullName)) + if (typeDefinition == null) return; - if (!typeDefinition.HasAttribute(KnownAttribute.Embedded)) + + if (attributeNames.Contains(typeDefinition.FullName)) + { + if (!typeDefinition.HasAttribute(KnownAttribute.Embedded)) + return; + } + else if (!nonEmbeddedAttributeNames.Contains(typeDefinition.FullName)) return; + if (typeDeclaration.Parent is NamespaceDeclaration ns && ns.Members.Count == 1) ns.Remove(); else