Skip to content

Support params keyword on non-array collections #3444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-frontends.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:

- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
dotnet-version: '9.0.x'
dotnet-quality: 'ga'

- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:

- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
dotnet-version: '9.0.x'
dotnet-quality: 'ga'

- name: Build
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

Expand Down
20 changes: 10 additions & 10 deletions ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ static Tester()
TesterPath = Path.GetDirectoryName(typeof(Tester).Assembly.Location);
TestCasePath = Path.Combine(TesterPath, "../../../../TestCases");
#if DEBUG
testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Debug/net8.0");
testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Debug/net9.0");
#else
testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Release/net8.0");
testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Release/net9.0");
#endif
// To parse: <Project><ItemGroup><PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0-3.final" />
packagesPropsFile = Path.Combine(TesterPath, "../../../../../Directory.Packages.props");
Expand Down Expand Up @@ -276,8 +276,8 @@ private static string ReplacePrivImplDetails(string il)
}

static readonly string coreRefAsmPath = new DotNetCorePathFinder(TargetFrameworkIdentifier.NET,
new Version(8, 0), "Microsoft.NETCore.App")
.GetReferenceAssemblyPath(".NETCoreApp,Version=v8.0");
new Version(9, 0), "Microsoft.NETCore.App")
.GetReferenceAssemblyPath(".NETCoreApp,Version=v9.0");

public static readonly string RefAsmPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
@"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2");
Expand Down Expand Up @@ -313,11 +313,9 @@ private static string ReplacePrivImplDetails(string il)
"Microsoft.VisualBasic.dll",
};

const string targetFrameworkAttributeSnippet = @"

[assembly: System.Runtime.Versioning.TargetFramework("".NETCoreApp,Version=v8.0"", FrameworkDisplayName = """")]

";
const string targetFrameworkAttributeSnippet = """
[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v9.0", FrameworkDisplayName = "")]
""";

static readonly Lazy<string> targetFrameworkAttributeSnippetFile = new Lazy<string>(GetTargetFrameworkAttributeSnippetFile);

Expand Down Expand Up @@ -391,6 +389,7 @@ public static List<string> GetPreprocessorSymbols(CompilerOptions flags)
preprocessorSymbols.Add("NET60");
preprocessorSymbols.Add("NET70");
preprocessorSymbols.Add("NET80");
preprocessorSymbols.Add("NET90");
}
preprocessorSymbols.Add("ROSLYN");
preprocessorSymbols.Add("CS60");
Expand Down Expand Up @@ -421,6 +420,7 @@ public static List<string> GetPreprocessorSymbols(CompilerOptions flags)
preprocessorSymbols.Add("CS100");
preprocessorSymbols.Add("CS110");
preprocessorSymbols.Add("CS120");
preprocessorSymbols.Add("CS130");
}
}
else if ((flags & CompilerOptions.UseMcsMask) != 0)
Expand Down Expand Up @@ -639,7 +639,7 @@ internal static DecompilerSettings GetSettings(CompilerOptions cscOptions)
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<LangVersion>13</LangVersion>
<RuntimeIdentifier Condition="$(IsWindowsX64) == true">win-x64</RuntimeIdentifier>
<RuntimeIdentifier Condition="$(IsWindowsARM64) == true">win-arm64</RuntimeIdentifier>

Expand All @@ -17,7 +18,7 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>

<NoWarn>1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602;8981</NoWarn>
<DefineConstants>ROSLYN;ROSLYN2;ROSLYN3;ROSLYN4;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120</DefineConstants>
<DefineConstants>ROSLYN;ROSLYN2;ROSLYN3;ROSLYN4;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120;CS130</DefineConstants>

<GenerateAssemblyVersionAttribute>False</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>False</GenerateAssemblyFileVersionAttribute>
Expand Down Expand Up @@ -141,6 +142,7 @@
<Compile Include="TestCases\Pretty\PointerArithmetic.cs" />
<Compile Include="TestCases\Pretty\Issue3439.cs" />
<Compile Include="TestCases\Pretty\Issue3442.cs" />
<Compile Include="TestCases\Pretty\ParamsCollections.cs" />
<None Include="TestCases\VBPretty\VBAutomaticEvents.vb" />
<Compile Include="TestCases\VBPretty\VBAutomaticEvents.cs" />
<Compile Include="TestCases\VBPretty\VBNonGenericForEach.cs" />
Expand Down
6 changes: 6 additions & 0 deletions ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,12 @@ public async Task ConstantsTests([ValueSource(nameof(defaultOptions))] CompilerO
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)
{
Expand Down
21 changes: 21 additions & 0 deletions ICSharpCode.Decompiler.Tests/TestCases/Pretty/ParamsCollections.cs
Original file line number Diff line number Diff line change
@@ -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<int> values)
{
}
public static void ParamsList(params List<int> values)
{
}
public static void ParamsReadOnlySpan(params ReadOnlySpan<int> values)
{
}
public static void ParamsSpan(params Span<int> values)
{
}
}
}
3 changes: 2 additions & 1 deletion ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public enum LanguageVersion
CSharp10_0 = 1000,
CSharp11_0 = 1100,
CSharp12_0 = 1200,
Preview = 1100,
CSharp13_0 = 1300,
Preview = 1300,
Latest = 0x7FFFFFFF
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ public class RemoveEmbeddedAttributes : DepthFirstAstVisitor, IAstTransform
"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",
Expand Down
24 changes: 24 additions & 0 deletions ICSharpCode.Decompiler/DecompilerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,16 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion)
refReadOnlyParameters = false;
usePrimaryConstructorSyntaxForNonRecordTypes = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp13_0)
{
paramsCollections = false;
}
}

public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
if (paramsCollections)
return CSharp.LanguageVersion.CSharp13_0;
if (refReadOnlyParameters || usePrimaryConstructorSyntaxForNonRecordTypes)
return CSharp.LanguageVersion.CSharp12_0;
if (scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators)
Expand Down Expand Up @@ -848,6 +854,24 @@ public bool ForEachWithGetEnumeratorExtension {
}
}

bool paramsCollections = true;

/// <summary>
/// Support params collections.
/// </summary>
[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;

/// <summary>
Expand Down
11 changes: 10 additions & 1 deletion ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,19 @@ public enum TypeSystemOptions
/// </summary>
RefReadOnlyParameters = 0x10000,
/// <summary>
/// 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.
/// </summary>
ParamsCollections = 0x20000,
/// <summary>
/// Default settings: typical options for the decompiler, with all C# languages features enabled.
/// </summary>
Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters
| RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods
| NativeIntegers | FunctionPointers | ScopedRef | NativeIntegersWithoutAttribute
| RefReadOnlyParameters
| RefReadOnlyParameters | ParamsCollections
}

/// <summary>
Expand Down Expand Up @@ -185,6 +192,8 @@ public static TypeSystemOptions GetOptions(DecompilerSettings settings)
typeSystemOptions |= TypeSystemOptions.NativeIntegersWithoutAttribute;
if (settings.RefReadOnlyParameters)
typeSystemOptions |= TypeSystemOptions.RefReadOnlyParameters;
if (settings.ParamsCollections)
typeSystemOptions |= TypeSystemOptions.ParamsCollections;
return typeSystemOptions;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ internal bool IgnoreAttribute(TopLevelTypeName attributeType, SymbolKind target)
case "RequiresLocationAttribute":
return (options & TypeSystemOptions.RefReadOnlyParameters) != 0
&& (target == SymbolKind.Parameter);
case "ParamCollectionAttribute":
return (options & TypeSystemOptions.ParamsCollections) != 0
&& (target == SymbolKind.Parameter);
default:
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public enum KnownAttribute

// Parameter attributes:
ParamArray,
ParamCollection,
In,
Out,
Optional,
Expand Down Expand Up @@ -166,6 +167,7 @@ public static class KnownAttributes
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)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ public LifetimeAnnotation Lifetime {

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 };
Expand All @@ -135,11 +141,17 @@ public LifetimeAnnotation Lifetime {

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;
}
}

Expand Down
1 change: 1 addition & 0 deletions ILSpy/Languages/CSharpLanguage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public override IReadOnlyList<LanguageVersion> LanguageVersions {
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;
Expand Down
19 changes: 9 additions & 10 deletions ILSpy/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions ILSpy/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.DecompileForEachWithGetEnumeratorExtension" xml:space="preserve">
<value>Decompile foreach statements with GetEnumerator extension methods</value>
</data>
<data name="DecompilerSettings.DecompileParamsCollections" xml:space="preserve">
<value>Decompile params collections</value>
</data>
<data name="DecompilerSettings.DecompileUseOfTheDynamicType" xml:space="preserve">
<value>Decompile use of the 'dynamic' type</value>
</data>
Expand Down
3 changes: 3 additions & 0 deletions ILSpy/Properties/Resources.zh-Hans.resx
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@
<data name="DecompilerSettings.DecompileForEachWithGetEnumeratorExtension" xml:space="preserve">
<value>反编译使用 GetEnumerator 扩展方法的 foreach 语句</value>
</data>
<data name="DecompilerSettings.DecompileParamsCollections" xml:space="preserve">
<value />
</data>
<data name="DecompilerSettings.DecompileUseOfTheDynamicType" xml:space="preserve">
<value>反编译 dynamic 类型</value>
</data>
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ How to build
- Make sure Windows PowerShell (at least version) 5.0 or [PowerShell](https://github.com/PowerShell/PowerShell) 7+ is installed.
- Clone the ILSpy repository using git.
- Execute `git submodule update --init --recursive` to download the ILSpy-Tests submodule (used by some test cases).
- Install Visual Studio (documented version: 17.8). You can install the necessary components in one of 3 ways:
- Install Visual Studio (documented version: 17.12). You can install the necessary components in one of 3 ways:
- Follow Microsoft's instructions for [importing a configuration](https://docs.microsoft.com/en-us/visualstudio/install/import-export-installation-configurations?view=vs-2022#import-a-configuration), and import the .vsconfig file located at the root of the solution.
- Alternatively, you can open the ILSpy solution (ILSpy.sln) and Visual Studio will [prompt you to install the missing components](https://docs.microsoft.com/en-us/visualstudio/install/import-export-installation-configurations?view=vs-2022#automatically-install-missing-components).
- Finally, you can manually install the necessary components via the Visual Studio Installer. The workloads/components are as follows:
- Workload ".NET Desktop Development". This workload includes the .NET Framework 4.8 SDK and the .NET Framework 4.7.2 targeting pack, as well as the [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) (ILSpy.csproj targets .NET 8.0, but we have net472 projects too). _Note: The optional components of this workload are not required for ILSpy_
- Workload ".NET Desktop Development". This workload includes the .NET Framework 4.8 SDK and the .NET Framework 4.7.2 targeting pack, as well as the [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) (ILSpy.csproj targets .NET 8.0, but we have net472 projects too). _Note: The optional components of this workload are not required for ILSpy_
- Workload "Visual Studio extension development" (ILSpy.sln contains a VS extension project) _Note: The optional components of this workload are not required for ILSpy_
- Individual Component "MSVC v143 - VS 2022 C++ x64/x86 build tools" (or similar)
- _The VC++ toolset is optional_; if present it is used for `editbin.exe` to modify the stack size used by ILSpy.exe from 1MB to 16MB, because the decompiler makes heavy use of recursion, where small stack sizes lead to problems in very complex methods.
Expand All @@ -66,12 +66,12 @@ How to build
- ILSpy.AddIn.slnf: for the Visual Studio plugin

**Note:** Visual Studio includes a version of the .NET SDK that is managed by the Visual Studio installer - once you update, it may get upgraded too.
Please note that ILSpy is only compatible with the .NET 8.0 SDK and Visual Studio will refuse to load some projects in the solution (and unit tests will fail).
If this problem occurs, please manually install the .NET 8.0 SDK from [here](https://dotnet.microsoft.com/download/dotnet/8.0).
Please note that ILSpy is only compatible with the .NET 9.0 SDK and Visual Studio will refuse to load some projects in the solution (and unit tests will fail).
If this problem occurs, please manually install the .NET 9.0 SDK from [here](https://dotnet.microsoft.com/download/dotnet/9.0).

#### Unix / Mac:

- Make sure [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) is installed.
- Make sure [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) is installed.
- Make sure [PowerShell](https://github.com/PowerShell/PowerShell) is installed (formerly known as PowerShell Core)
- Clone the repository using git.
- Execute `git submodule update --init --recursive` to download the ILSpy-Tests submodule (used by some test cases).
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "8.0.100",
"version": "9.0.100",
"rollForward": "major",
"allowPrerelease": true
}
Expand Down
Loading