From b05b9231ecabe62041198da57fe55f2edbc941aa Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:45:21 -0700 Subject: [PATCH] Support params keyword on non-array collections --- .../Helpers/Tester.cs | 3 ++- .../ICSharpCode.Decompiler.Tests.csproj | 4 +++- .../PrettyTestRunner.cs | 6 +++++ .../TestCases/Pretty/ParamsCollections.cs | 21 ++++++++++++++++ .../CSharp/CSharpLanguageVersion.cs | 3 ++- .../Transforms/EscapeInvalidIdentifiers.cs | 1 + ICSharpCode.Decompiler/DecompilerSettings.cs | 24 +++++++++++++++++++ .../TypeSystem/DecompilerTypeSystem.cs | 11 ++++++++- .../Implementation/AttributeListBuilder.cs | 3 +++ .../Implementation/KnownAttributes.cs | 2 ++ .../Implementation/MetadataParameter.cs | 18 +++++++++++--- ILSpy/Languages/CSharpLanguage.cs | 1 + ILSpy/Properties/Resources.Designer.cs | 9 +++++++ ILSpy/Properties/Resources.resx | 3 +++ ILSpy/Properties/Resources.zh-Hans.resx | 3 +++ 15 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/ParamsCollections.cs diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 00b44f989..4d84abad6 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -459,6 +459,7 @@ namespace System.Runtime.CompilerServices preprocessorSymbols.Add("CS100"); preprocessorSymbols.Add("CS110"); preprocessorSymbols.Add("CS120"); + preprocessorSymbols.Add("CS130"); } } else if ((flags & CompilerOptions.UseMcsMask) != 0) @@ -705,7 +706,7 @@ namespace System.Runtime.CompilerServices CompilerOptions.UseRoslyn1_3_2 => CSharp.LanguageVersion.CSharp6, CompilerOptions.UseRoslyn2_10_0 => CSharp.LanguageVersion.CSharp7_3, CompilerOptions.UseRoslyn3_11_0 => CSharp.LanguageVersion.CSharp9_0, - _ => cscOptions.HasFlag(CompilerOptions.Preview) ? CSharp.LanguageVersion.Latest : CSharp.LanguageVersion.CSharp12_0, + _ => cscOptions.HasFlag(CompilerOptions.Preview) ? CSharp.LanguageVersion.Latest : CSharp.LanguageVersion.CSharp13_0, }; DecompilerSettings settings = new(langVersion) { // Never use file-scoped namespaces diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 72c4aaaa7..d581a3ffd 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -8,6 +8,7 @@ net10.0-windows + 13 win-x64 win-arm64 @@ -17,7 +18,7 @@ True 1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602;8981 - ROSLYN;ROSLYN2;ROSLYN3;ROSLYN4;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120 + ROSLYN;ROSLYN2;ROSLYN3;ROSLYN4;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120;CS130 False False @@ -146,6 +147,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index ae18373fa..686233381 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -605,6 +605,12 @@ namespace ICSharpCode.Decompiler.Tests await RunForLibrary(cscOptions: cscOptions); } + [Test] + public async Task ParamsCollections([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + [Test] public async Task Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ParamsCollections.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ParamsCollections.cs new file mode 100644 index 000000000..69b0aa2e8 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ParamsCollections.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + public static class ParamsCollections + { + public static void ParamsEnumerable(params IEnumerable values) + { + } + public static void ParamsList(params List values) + { + } + public static void ParamsReadOnlySpan(params ReadOnlySpan values) + { + } + public static void ParamsSpan(params Span values) + { + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs b/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs index 019774580..65e7fdceb 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs @@ -35,7 +35,8 @@ namespace ICSharpCode.Decompiler.CSharp CSharp10_0 = 1000, CSharp11_0 = 1100, CSharp12_0 = 1200, - Preview = 1100, + CSharp13_0 = 1300, + Preview = 1300, Latest = 0x7FFFFFFF } } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs index 907fb807f..288e4d08d 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs @@ -166,6 +166,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms "System.Runtime.CompilerServices.NullableAttribute", "System.Runtime.CompilerServices.NullableContextAttribute", "System.Runtime.CompilerServices.NativeIntegerAttribute", + "System.Runtime.CompilerServices.ParamCollectionAttribute", "System.Runtime.CompilerServices.RefSafetyRulesAttribute", "System.Runtime.CompilerServices.ScopedRefAttribute", "System.Runtime.CompilerServices.RequiresLocationAttribute", diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index bbf7a0055..980fcef13 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -166,10 +166,16 @@ namespace ICSharpCode.Decompiler usePrimaryConstructorSyntaxForNonRecordTypes = false; inlineArrays = false; } + if (languageVersion < CSharp.LanguageVersion.CSharp13_0) + { + paramsCollections = false; + } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { + if (paramsCollections) + return CSharp.LanguageVersion.CSharp13_0; if (refReadOnlyParameters || usePrimaryConstructorSyntaxForNonRecordTypes || inlineArrays) return CSharp.LanguageVersion.CSharp12_0; if (scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators) @@ -849,6 +855,24 @@ namespace ICSharpCode.Decompiler } } + bool paramsCollections = true; + + /// + /// Support params collections. + /// + [Category("C# 13.0 / VS 2022.12")] + [Description("DecompilerSettings.DecompileParamsCollections")] + public bool ParamsCollections { + get { return paramsCollections; } + set { + if (paramsCollections != value) + { + paramsCollections = value; + OnPropertyChanged(); + } + } + } + bool lockStatement = true; /// diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index fa849ef9a..03feb0f0d 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -138,12 +138,19 @@ namespace ICSharpCode.Decompiler.TypeSystem /// RefReadOnlyParameters = 0x10000, /// + /// If this option is active, [ParamCollectionAttribute] on parameters is removed + /// and parameters are marked as params. + /// Otherwise, the attribute is preserved but the parameters are not marked + /// as if it was a normal parameter without any attributes. + /// + ParamsCollections = 0x20000, + /// /// Default settings: typical options for the decompiler, with all C# languages features enabled. /// Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters | RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods | NativeIntegers | FunctionPointers | ScopedRef | NativeIntegersWithoutAttribute - | RefReadOnlyParameters + | RefReadOnlyParameters | ParamsCollections } /// @@ -185,6 +192,8 @@ namespace ICSharpCode.Decompiler.TypeSystem typeSystemOptions |= TypeSystemOptions.NativeIntegersWithoutAttribute; if (settings.RefReadOnlyParameters) typeSystemOptions |= TypeSystemOptions.RefReadOnlyParameters; + if (settings.ParamsCollections) + typeSystemOptions |= TypeSystemOptions.ParamsCollections; return typeSystemOptions; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs index 66d77a735..15624ab98 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs @@ -258,6 +258,9 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation case "RequiresLocationAttribute": return (options & TypeSystemOptions.RefReadOnlyParameters) != 0 && (target == SymbolKind.Parameter); + case "ParamCollectionAttribute": + return (options & TypeSystemOptions.ParamsCollections) != 0 + && (target == SymbolKind.Parameter); default: return false; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index a9e59e0d9..a66813880 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -87,6 +87,7 @@ namespace ICSharpCode.Decompiler.TypeSystem // Parameter attributes: ParamArray, + ParamCollection, In, Out, Optional, @@ -169,6 +170,7 @@ namespace ICSharpCode.Decompiler.TypeSystem new TopLevelTypeName("System.Runtime.CompilerServices", nameof(IndexerNameAttribute)), // Parameter attributes: new TopLevelTypeName("System", nameof(ParamArrayAttribute)), + new TopLevelTypeName("System.Runtime.CompilerServices", "ParamCollectionAttribute"), new TopLevelTypeName("System.Runtime.InteropServices", nameof(InAttribute)), new TopLevelTypeName("System.Runtime.InteropServices", nameof(OutAttribute)), new TopLevelTypeName("System.Runtime.InteropServices", nameof(OptionalAttribute)), diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs index 8089b1eae..c82076394 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs @@ -125,6 +125,12 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation var metadata = module.metadata; var parameterDef = metadata.GetParameter(handle); + if ((module.TypeSystemOptions & TypeSystemOptions.ParamsCollections) != 0 + && parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.ParamCollection)) + { + // params collections are implicitly scoped + return default; + } if (parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.ScopedRef)) { return new LifetimeAnnotation { ScopedRef = true }; @@ -135,11 +141,17 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public bool IsParams { get { - if (Type.Kind != TypeKind.Array) - return false; var metadata = module.metadata; var parameterDef = metadata.GetParameter(handle); - return parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.ParamArray); + if (Type.Kind == TypeKind.Array) + { + return parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.ParamArray); + } + if (module.TypeSystemOptions.HasFlag(TypeSystemOptions.ParamsCollections)) + { + return parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.ParamCollection); + } + return false; } } diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index 73336640e..6d66c4736 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -116,6 +116,7 @@ namespace ICSharpCode.ILSpy new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp10_0.ToString(), "C# 10.0 / VS 2022"), new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp11_0.ToString(), "C# 11.0 / VS 2022.4"), new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp12_0.ToString(), "C# 12.0 / VS 2022.8"), + new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp13_0.ToString(), "C# 13.0 / VS 2022.12"), }; } return versions; diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index d51f6e4b1..d31af0b12 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -918,6 +918,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Decompile params collections. + /// + public static string DecompilerSettings_DecompileParamsCollections { + get { + return ResourceManager.GetString("DecompilerSettings.DecompileParamsCollections", resourceCulture); + } + } + /// /// Looks up a localized string similar to Decompile use of the 'dynamic' type. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index be83f54ed..840c92fb1 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -327,6 +327,9 @@ Are you sure you want to continue? Decompile foreach statements with GetEnumerator extension methods + + Decompile params collections + Decompile use of the 'dynamic' type diff --git a/ILSpy/Properties/Resources.zh-Hans.resx b/ILSpy/Properties/Resources.zh-Hans.resx index 8955a00ba..3973a4805 100644 --- a/ILSpy/Properties/Resources.zh-Hans.resx +++ b/ILSpy/Properties/Resources.zh-Hans.resx @@ -315,6 +315,9 @@ 反编译使用 GetEnumerator 扩展方法的 foreach 语句 + + + 反编译 dynamic 类型