From e4ebe2060213fa071b80d8289e00c6c0f1214be7 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 25 Apr 2025 19:39:15 +0200 Subject: [PATCH 01/14] Upgrade dotnet target framework to net10 --- .github/workflows/build-frontends.yml | 4 ++-- .github/workflows/build-ilspy.yml | 18 +++++++++--------- .github/workflows/codeql-analysis.yml | 4 ++-- BuildTools/pre-commit | 4 ++-- .../ICSharpCode.BamlDecompiler.csproj | 6 +++--- ICSharpCode.BamlDecompiler/packages.lock.json | 2 +- .../ICSharpCode.Decompiler.TestRunner.csproj | 2 +- ICSharpCode.Decompiler.Tests/Helpers/Tester.cs | 10 +++++----- .../ICSharpCode.Decompiler.Tests.csproj | 2 +- .../ICSharpCode.Decompiler.csproj | 2 +- .../DecompilerVersionInfo.template.cs | 6 +++--- .../ICSharpCode.ILSpyCmd.csproj | 4 ++-- ICSharpCode.ILSpyCmd/packages.lock.json | 2 +- ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj | 4 ++-- .../MermaidDiagrammer/html/.vscode/tasks.json | 2 +- ICSharpCode.ILSpyX/packages.lock.json | 2 +- ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj | 4 ++-- ILSpy.AddIn/ILSpy.AddIn.csproj | 2 +- .../ILSpy.BamlDecompiler.Tests.csproj | 2 +- .../ILSpy.BamlDecompiler.csproj | 2 +- ILSpy.Installer/setup.cs | 2 +- ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj | 2 +- ILSpy.Tests/ILSpy.Tests.csproj | 2 +- ILSpy.sln | 1 + ILSpy/ILSpy.csproj | 2 +- TestPlugin/TestPlugin.csproj | 2 +- global.json | 2 +- publish.ps1 | 6 +++--- publishlocaldev.ps1 | 4 ++-- 29 files changed, 54 insertions(+), 53 deletions(-) diff --git a/.github/workflows/build-frontends.yml b/.github/workflows/build-frontends.yml index 894202f2c..5db371487 100644 --- a/.github/workflows/build-frontends.yml +++ b/.github/workflows/build-frontends.yml @@ -21,8 +21,8 @@ jobs: - uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.0.x' - dotnet-quality: 'ga' + dotnet-version: '10.0.x' + dotnet-quality: 'preview' - name: Install dependencies run: dotnet restore ILSpy.XPlat.slnf diff --git a/.github/workflows/build-ilspy.yml b/.github/workflows/build-ilspy.yml index cf86850ae..967db1129 100644 --- a/.github/workflows/build-ilspy.yml +++ b/.github/workflows/build-ilspy.yml @@ -34,8 +34,8 @@ jobs: - uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.0.x' - dotnet-quality: 'ga' + dotnet-version: '10.0.x' + dotnet-quality: 'preview' env: DOTNET_INSTALL_DIR: ${{ runner.temp }}/.dotnet DOTNET_ROOT: ${{ runner.temp }}/.dotnet @@ -44,7 +44,7 @@ jobs: uses: microsoft/setup-msbuild@v2 - name: Install dotnet-format - run: dotnet tool install -g dotnet-format --version "8.3.546805" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json + run: dotnet tool install -g dotnet-format --version "9.0.520307" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json - name: Install wix (locked version) run: dotnet tool install --global wix --version 6.0.0 @@ -65,9 +65,9 @@ jobs: - name: Execute unit tests run: dotnet test --logger "trx;LogFileName=${{ matrix.configuration }}.trx" --results-directory test-results $env:Tests1 $env:Tests2 $env:Tests3 env: - Tests1: ICSharpCode.Decompiler.Tests\bin\${{ matrix.configuration }}\net8.0-windows\win-x64\ICSharpCode.Decompiler.Tests.dll - Tests2: ILSpy.Tests\bin\${{ matrix.configuration }}\net8.0-windows\ILSpy.Tests.dll - Tests3: ILSpy.BamlDecompiler.Tests\bin\${{ matrix.configuration }}\net8.0-windows\win-x64\ILSpy.BamlDecompiler.Tests.dll + Tests1: ICSharpCode.Decompiler.Tests\bin\${{ matrix.configuration }}\net10.0-windows\win-x64\ICSharpCode.Decompiler.Tests.dll + Tests2: ILSpy.Tests\bin\${{ matrix.configuration }}\net10.0-windows\ILSpy.Tests.dll + Tests3: ILSpy.BamlDecompiler.Tests\bin\${{ matrix.configuration }}\net10.0-windows\win-x64\ILSpy.BamlDecompiler.Tests.dll - name: Upload Test Logs uses: actions/upload-artifact@v4 @@ -94,7 +94,7 @@ jobs: git diff --exit-code - name: Zip ILSpy (framework-dependent) - run: 7z a -tzip $env:StagingDirectory\ILSpy_binaries.zip .\ILSpy\bin\${{ matrix.configuration }}\net8.0-windows\*.dll .\ILSpy\bin\${{ matrix.configuration }}\net8.0-windows\*.exe .\ILSpy\bin\${{ matrix.configuration }}\net8.0-windows\*.config .\ILSpy\bin\${{ matrix.configuration }}\net8.0-windows\*.json .\ILSpy\bin\${{ matrix.configuration }}\net8.0-windows\*\ILSpy.resources.dll .\ILSpy\bin\${{ matrix.configuration }}\net8.0-windows\*\ILSpy.ReadyToRun.Plugin.resources.dll + run: 7z a -tzip $env:StagingDirectory\ILSpy_binaries.zip .\ILSpy\bin\${{ matrix.configuration }}\net10.0-windows\*.dll .\ILSpy\bin\${{ matrix.configuration }}\net10.0-windows\*.exe .\ILSpy\bin\${{ matrix.configuration }}\net10.0-windows\*.config .\ILSpy\bin\${{ matrix.configuration }}\net10.0-windows\*.json .\ILSpy\bin\${{ matrix.configuration }}\net10.0-windows\*\ILSpy.resources.dll .\ILSpy\bin\${{ matrix.configuration }}\net10.0-windows\*\ILSpy.ReadyToRun.Plugin.resources.dll - name: Publish x64/arm64 framework-dependent/self-contained shell: pwsh @@ -102,11 +102,11 @@ jobs: - name: Zip ILSpy Release (x64 self-contained) if: matrix.configuration == 'release' - run: 7z a -tzip $env:StagingDirectory\ILSpy_selfcontained_x64.zip .\ILSpy\bin\Release\net8.0-windows\win-x64\publish\selfcontained\* + run: 7z a -tzip $env:StagingDirectory\ILSpy_selfcontained_x64.zip .\ILSpy\bin\Release\net10.0-windows\win-x64\publish\selfcontained\* - name: Zip ILSpy Release (arm64 framework-dependent) if: matrix.configuration == 'release' - run: 7z a -tzip $env:StagingDirectory\ILSpy_binaries_arm64.zip .\ILSpy\bin\Release\net8.0-windows\win-arm64\publish\fwdependent\* + run: 7z a -tzip $env:StagingDirectory\ILSpy_binaries_arm64.zip .\ILSpy\bin\Release\net10.0-windows\win-arm64\publish\fwdependent\* - name: Pack NuGets if: matrix.configuration == 'release' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1508ed57f..64593341d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,8 +37,8 @@ jobs: - uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.0.x' - dotnet-quality: 'ga' + dotnet-version: '10.0.x' + dotnet-quality: 'preview' - name: Build run: dotnet build ILSpy.XPlat.slnf --configuration Release diff --git a/BuildTools/pre-commit b/BuildTools/pre-commit index 1067d5581..8d455651a 100644 --- a/BuildTools/pre-commit +++ b/BuildTools/pre-commit @@ -5,11 +5,11 @@ set -eu -DOTNET_FORMAT_VERSION=8.3.546805 +DOTNET_FORMAT_VERSION=9.0.520307 DOTNET_PATH="$LOCALAPPDATA/ICSharpCode/ILSpy/dotnet-format-$DOTNET_FORMAT_VERSION" if [ ! -d "$DOTNET_PATH" ]; then echo "Downloading dotnet-format $DOTNET_FORMAT_VERSION..." - dotnet tool install --tool-path "$DOTNET_PATH" dotnet-format --version "$DOTNET_FORMAT_VERSION" --add-source "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json" + dotnet tool install --tool-path "$DOTNET_PATH" dotnet-format --version "$DOTNET_FORMAT_VERSION" --add-source "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json" fi "$DOTNET_PATH/dotnet-format.exe" --version diff --git a/ICSharpCode.BamlDecompiler/ICSharpCode.BamlDecompiler.csproj b/ICSharpCode.BamlDecompiler/ICSharpCode.BamlDecompiler.csproj index 5bee31b6c..b85274aca 100644 --- a/ICSharpCode.BamlDecompiler/ICSharpCode.BamlDecompiler.csproj +++ b/ICSharpCode.BamlDecompiler/ICSharpCode.BamlDecompiler.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 True ..\ICSharpCode.Decompiler\ICSharpCode.Decompiler.snk @@ -14,7 +14,7 @@ ICSharpCode.BamlDecompiler - 8.0.0.0-noversion + 10.0.0.0-noversion ILSpy BAML Decompiler ILSpy Contributors MIT @@ -44,7 +44,7 @@ true - true + true diff --git a/ICSharpCode.BamlDecompiler/packages.lock.json b/ICSharpCode.BamlDecompiler/packages.lock.json index 06e809065..479c3b8d2 100644 --- a/ICSharpCode.BamlDecompiler/packages.lock.json +++ b/ICSharpCode.BamlDecompiler/packages.lock.json @@ -1,7 +1,7 @@ { "version": 2, "dependencies": { - "net8.0": { + "net10.0": { "Microsoft.Sbom.Targets": { "type": "Direct", "requested": "[3.1.0, )", diff --git a/ICSharpCode.Decompiler.TestRunner/ICSharpCode.Decompiler.TestRunner.csproj b/ICSharpCode.Decompiler.TestRunner/ICSharpCode.Decompiler.TestRunner.csproj index af31fefa2..f9929d13f 100644 --- a/ICSharpCode.Decompiler.TestRunner/ICSharpCode.Decompiler.TestRunner.csproj +++ b/ICSharpCode.Decompiler.TestRunner/ICSharpCode.Decompiler.TestRunner.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 enable diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 4583a5fae..d1c00d9cc 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -105,9 +105,9 @@ namespace ICSharpCode.Decompiler.Tests.Helpers 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/net10.0"); #else - testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Release/net8.0"); + testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Release/net10.0"); #endif // To parse: packagesPropsFile = Path.Combine(TesterPath, "../../../../../Directory.Packages.props"); @@ -277,8 +277,8 @@ namespace ICSharpCode.Decompiler.Tests.Helpers } static readonly string coreRefAsmPath = new DotNetCorePathFinder(TargetFrameworkIdentifier.NET, - new Version(8, 0), "Microsoft.NETCore.App") - .GetReferenceAssemblyPath(".NETCoreApp,Version=v8.0"); + new Version(10, 0), "Microsoft.NETCore.App") + .GetReferenceAssemblyPath(".NETCoreApp,Version=v10.0"); public static readonly string RefAsmPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2"); @@ -316,7 +316,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers const string targetFrameworkAttributeSnippet = @" -[assembly: System.Runtime.Versioning.TargetFramework("".NETCoreApp,Version=v8.0"", FrameworkDisplayName = """")] +[assembly: System.Runtime.Versioning.TargetFramework("".NETCoreApp,Version=v10.0"", FrameworkDisplayName = """")] "; diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 110b3bb36..72c4aaaa7 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -7,7 +7,7 @@ - net8.0-windows + net10.0-windows win-x64 win-arm64 diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 7e2e19341..53daacdc6 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -51,7 +51,7 @@ true - true + true diff --git a/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs b/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs index f4a587815..cc4e60030 100644 --- a/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs +++ b/ICSharpCode.Decompiler/Properties/DecompilerVersionInfo.template.cs @@ -1,10 +1,10 @@ public static class DecompilerVersionInfo { - public const string Major = "9"; - public const string Minor = "1"; + public const string Major = "10"; + public const string Minor = "0"; public const string Build = "0"; public const string Revision = "$INSERTREVISION$"; - public const string VersionName = null; + public const string VersionName = "preview1"; public const string FullVersion = Major + "." + Minor + "." + Build + ".$INSERTREVISION$$INSERTBRANCHPOSTFIX$$INSERTVERSIONNAMEPOSTFIX$"; public const string FullVersionWithShortCommitHash = FullVersion + "+$INSERTSHORTCOMMITHASH$"; diff --git a/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj b/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj index aba229ad0..528650279 100644 --- a/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj +++ b/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 true true true @@ -35,7 +35,7 @@ true - true + true diff --git a/ICSharpCode.ILSpyCmd/packages.lock.json b/ICSharpCode.ILSpyCmd/packages.lock.json index 891f86671..5b63ee7c5 100644 --- a/ICSharpCode.ILSpyCmd/packages.lock.json +++ b/ICSharpCode.ILSpyCmd/packages.lock.json @@ -1,7 +1,7 @@ { "version": 2, "dependencies": { - "net8.0": { + "net10.0": { "McMaster.Extensions.Hosting.CommandLine": { "type": "Direct", "requested": "[4.1.1, )", diff --git a/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj b/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj index 3bcd8c8e1..b0e9423b5 100644 --- a/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj +++ b/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable true nullable @@ -51,7 +51,7 @@ true - true + true diff --git a/ICSharpCode.ILSpyX/MermaidDiagrammer/html/.vscode/tasks.json b/ICSharpCode.ILSpyX/MermaidDiagrammer/html/.vscode/tasks.json index 2249378fb..b37879f67 100644 --- a/ICSharpCode.ILSpyX/MermaidDiagrammer/html/.vscode/tasks.json +++ b/ICSharpCode.ILSpyX/MermaidDiagrammer/html/.vscode/tasks.json @@ -9,7 +9,7 @@ "group": "build", "type": "shell", "command": [ - "$folder = '../../../ICSharpCode.ILSpyCmd/bin/Debug/net8.0/';", // to avoid repetition + "$folder = '../../../ICSharpCode.ILSpyCmd/bin/Debug/net10.0/';", // to avoid repetition "$exePath = $folder + 'ilspycmd.exe';", "$assemblyPath = $folder + 'ICSharpCode.Decompiler.dll';", // comes with XML docs for testing the integration "if (Test-Path $exePath) {", diff --git a/ICSharpCode.ILSpyX/packages.lock.json b/ICSharpCode.ILSpyX/packages.lock.json index aac3060dd..08db0ba75 100644 --- a/ICSharpCode.ILSpyX/packages.lock.json +++ b/ICSharpCode.ILSpyX/packages.lock.json @@ -1,7 +1,7 @@ { "version": 2, "dependencies": { - "net8.0": { + "net10.0": { "K4os.Compression.LZ4": { "type": "Direct", "requested": "[1.3.8, )", diff --git a/ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj b/ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj index c5d27caea..2a249fe7b 100644 --- a/ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj +++ b/ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj @@ -75,8 +75,8 @@ - ..\ILSpy\bin\$(Configuration)\net8.0-windows\win-x64\publish\fwdependent\ - ..\ILSpy\bin\$(Configuration)\net8.0-windows\win-arm64\publish\fwdependent\ + ..\ILSpy\bin\$(Configuration)\net10.0-windows\win-x64\publish\fwdependent\ + ..\ILSpy\bin\$(Configuration)\net10.0-windows\win-arm64\publish\fwdependent\ diff --git a/ILSpy.AddIn/ILSpy.AddIn.csproj b/ILSpy.AddIn/ILSpy.AddIn.csproj index bdcf4d021..d8d537f8f 100644 --- a/ILSpy.AddIn/ILSpy.AddIn.csproj +++ b/ILSpy.AddIn/ILSpy.AddIn.csproj @@ -81,7 +81,7 @@ - ..\ILSpy\bin\$(Configuration)\net8.0-windows\win-x64\publish\fwdependent\ + ..\ILSpy\bin\$(Configuration)\net10.0-windows\win-x64\publish\fwdependent\ diff --git a/ILSpy.BamlDecompiler.Tests/ILSpy.BamlDecompiler.Tests.csproj b/ILSpy.BamlDecompiler.Tests/ILSpy.BamlDecompiler.Tests.csproj index 849406f39..3c758f5ae 100644 --- a/ILSpy.BamlDecompiler.Tests/ILSpy.BamlDecompiler.Tests.csproj +++ b/ILSpy.BamlDecompiler.Tests/ILSpy.BamlDecompiler.Tests.csproj @@ -7,7 +7,7 @@ - net8.0-windows + net10.0-windows win-x64 win-arm64 diff --git a/ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj b/ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj index 63950dda4..46c5cab87 100644 --- a/ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj +++ b/ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj @@ -2,7 +2,7 @@ ILSpy.BamlDecompiler.Plugin - net8.0-windows + net10.0-windows win-x64;win-arm64 false False diff --git a/ILSpy.Installer/setup.cs b/ILSpy.Installer/setup.cs index a5c8b9f98..eeabecf7f 100644 --- a/ILSpy.Installer/setup.cs +++ b/ILSpy.Installer/setup.cs @@ -25,7 +25,7 @@ namespace ILSpy.Installer #else var buildPlatform = "x64"; #endif - var buildOutputDir = $@"ILSpy\bin\{buildConfiguration}\net8.0-windows\win-{buildPlatform}\publish\fwdependent"; + var buildOutputDir = $@"ILSpy\bin\{buildConfiguration}\net10.0-windows\win-{buildPlatform}\publish\fwdependent"; var project = new Project("ILSpy", new InstallDir(@"%LocalAppData%\Programs\ILSpy", diff --git a/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj b/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj index dfe925ddd..f60326eb0 100644 --- a/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj +++ b/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj @@ -3,7 +3,7 @@ ILSpy.ReadyToRun.Plugin - net8.0-windows + net10.0-windows win-x64;win-arm64 False en-US diff --git a/ILSpy.Tests/ILSpy.Tests.csproj b/ILSpy.Tests/ILSpy.Tests.csproj index 7fff3f2eb..5c361e11c 100644 --- a/ILSpy.Tests/ILSpy.Tests.csproj +++ b/ILSpy.Tests/ILSpy.Tests.csproj @@ -2,7 +2,7 @@ - net8.0-windows + net10.0-windows false AutoGeneratedProgram diff --git a/ILSpy.sln b/ILSpy.sln index 099c49504..27a5a1fd6 100644 --- a/ILSpy.sln +++ b/ILSpy.sln @@ -41,6 +41,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig Directory.Build.props = Directory.Build.props Directory.Packages.props = Directory.Packages.props + global.json = global.json EndProjectSection EndProject Global diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index aa5a7264a..3896678ed 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -2,7 +2,7 @@ WinExe - net8.0-windows + net10.0-windows major win-x64;win-arm64 False diff --git a/TestPlugin/TestPlugin.csproj b/TestPlugin/TestPlugin.csproj index c527fbcae..dbfb5f9d5 100644 --- a/TestPlugin/TestPlugin.csproj +++ b/TestPlugin/TestPlugin.csproj @@ -1,7 +1,7 @@  - net8.0-windows + net10.0-windows Test.Plugin true true diff --git a/global.json b/global.json index 6a5d4badd..42b44a1ed 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "10.0.0", "rollForward": "major", "allowPrerelease": true } diff --git a/publish.ps1 b/publish.ps1 index 6854d2f93..b5992d53d 100644 --- a/publish.ps1 +++ b/publish.ps1 @@ -1,6 +1,6 @@ -$output_arm64 = "./ILSpy/bin/Release/net8.0-windows/win-arm64/publish/fwdependent" -$output_x64 = "./ILSpy/bin/Release/net8.0-windows/win-x64/publish/fwdependent" -$output_x64_selfcontained = "./ILSpy/bin/Release/net8.0-windows/win-x64/publish/selfcontained" +$output_arm64 = "./ILSpy/bin/Release/net10.0-windows/win-arm64/publish/fwdependent" +$output_x64 = "./ILSpy/bin/Release/net10.0-windows/win-x64/publish/fwdependent" +$output_x64_selfcontained = "./ILSpy/bin/Release/net10.0-windows/win-x64/publish/selfcontained" dotnet publish ./ILSpy/ILSpy.csproj -c Release --no-restore --no-self-contained -r win-arm64 -o $output_arm64 dotnet publish ./ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj -c Release --no-restore --no-self-contained -r win-arm64 -o $output_arm64 diff --git a/publishlocaldev.ps1 b/publishlocaldev.ps1 index 165423c2a..baf539b17 100644 --- a/publishlocaldev.ps1 +++ b/publishlocaldev.ps1 @@ -1,12 +1,12 @@ # For local development of the VSIX package - build and publish (VS2022 also needs arm64) -$output_x64 = "./ILSpy/bin/Release/net8.0-windows/win-x64/publish/fwdependent" +$output_x64 = "./ILSpy/bin/Release/net10.0-windows/win-x64/publish/fwdependent" dotnet publish ./ILSpy/ILSpy.csproj -c Release --no-restore --no-self-contained -r win-x64 -o $output_x64 dotnet publish ./ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj -c Release --no-restore --no-self-contained -r win-x64 -o $output_x64 dotnet publish ./ILSpy.BamlDecompiler/ILSpy.BamlDecompiler.csproj -c Release --no-restore --no-self-contained -r win-x64 -o $output_x64 -$output_arm64 = "./ILSpy/bin/Release/net8.0-windows/win-arm64/publish/fwdependent" +$output_arm64 = "./ILSpy/bin/Release/net10.0-windows/win-arm64/publish/fwdependent" dotnet publish ./ILSpy/ILSpy.csproj -c Release --no-restore --no-self-contained -r win-arm64 -o $output_arm64 dotnet publish ./ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj -c Release --no-restore --no-self-contained -r win-arm64 -o $output_arm64 From a85ac055b563393826a4a755e0c36e800bebc448 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 26 Apr 2025 20:18:59 +0200 Subject: [PATCH 02/14] Disable package pruning by force --- .github/workflows/build-frontends.yml | 2 +- .github/workflows/build-ilspy.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- ICSharpCode.ILSpyCmd/packages.lock.json | 9 +++------ ICSharpCode.ILSpyX/packages.lock.json | 12 ++++++------ 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-frontends.yml b/.github/workflows/build-frontends.yml index 5db371487..55f6413eb 100644 --- a/.github/workflows/build-frontends.yml +++ b/.github/workflows/build-frontends.yml @@ -25,7 +25,7 @@ jobs: dotnet-quality: 'preview' - name: Install dependencies - run: dotnet restore ILSpy.XPlat.slnf + run: dotnet restore ILSpy.XPlat.slnf -p:RestoreEnablePackagePruning=false - name: Build Debug run: dotnet msbuild ILSpy.XPlat.slnf -p:Configuration=Debug -bl:Debug.binlog diff --git a/.github/workflows/build-ilspy.yml b/.github/workflows/build-ilspy.yml index 967db1129..c684ff8c0 100644 --- a/.github/workflows/build-ilspy.yml +++ b/.github/workflows/build-ilspy.yml @@ -57,7 +57,7 @@ jobs: Get-ChildItem Env: | Where-Object {$_.Name -Match "^ILSPY_"} | %{ echo "$($_.Name)=$($_.Value)" } | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append - name: Restore the application - run: msbuild ILSpy.sln /t:Restore /p:Configuration=${{ matrix.configuration }} /p:Platform=$env:BuildPlatform + run: msbuild ILSpy.sln /t:Restore /p:RestoreEnablePackagePruning=false /p:Configuration=${{ matrix.configuration }} /p:Platform=$env:BuildPlatform - name: Build run: msbuild ILSpy.sln /p:Configuration=${{ matrix.configuration }} /p:Platform=$env:BuildPlatform /m diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 64593341d..d40870c16 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -41,7 +41,7 @@ jobs: dotnet-quality: 'preview' - name: Build - run: dotnet build ILSpy.XPlat.slnf --configuration Release + run: dotnet build ILSpy.XPlat.slnf --configuration Release -p:RestoreEnablePackagePruning=false - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 diff --git a/ICSharpCode.ILSpyCmd/packages.lock.json b/ICSharpCode.ILSpyCmd/packages.lock.json index 5b63ee7c5..5449e0bd4 100644 --- a/ICSharpCode.ILSpyCmd/packages.lock.json +++ b/ICSharpCode.ILSpyCmd/packages.lock.json @@ -425,8 +425,8 @@ "System.Collections.Immutable": { "type": "CentralTransitive", "requested": "[9.0.4, )", - "resolved": "9.0.4", - "contentHash": "wfm2NgK22MmBe5qJjp52qzpkeDZKb4l9LbdubhZSehY1z4LS+lld6R+B+UQNb2AZRHu/QJlHxEUcRst5hIEejg==" + "resolved": "8.0.0", + "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" }, "System.Composition.AttributedModel": { "type": "CentralTransitive", @@ -438,10 +438,7 @@ "type": "CentralTransitive", "requested": "[9.0.4, )", "resolved": "9.0.4", - "contentHash": "qeJNsMmZPc/Lieg0Md+D4F6LoLcxV3b9QsUNmBRXc2ZVOkMbAcwuO9l2jbQFv3n+fLiHJilN8v6i5aJNivjrCQ==", - "dependencies": { - "System.Collections.Immutable": "9.0.4" - } + "contentHash": "qeJNsMmZPc/Lieg0Md+D4F6LoLcxV3b9QsUNmBRXc2ZVOkMbAcwuO9l2jbQFv3n+fLiHJilN8v6i5aJNivjrCQ==" }, "System.Runtime.CompilerServices.Unsafe": { "type": "CentralTransitive", diff --git a/ICSharpCode.ILSpyX/packages.lock.json b/ICSharpCode.ILSpyX/packages.lock.json index 08db0ba75..d17894185 100644 --- a/ICSharpCode.ILSpyX/packages.lock.json +++ b/ICSharpCode.ILSpyX/packages.lock.json @@ -40,10 +40,7 @@ "type": "Direct", "requested": "[9.0.4, )", "resolved": "9.0.4", - "contentHash": "qeJNsMmZPc/Lieg0Md+D4F6LoLcxV3b9QsUNmBRXc2ZVOkMbAcwuO9l2jbQFv3n+fLiHJilN8v6i5aJNivjrCQ==", - "dependencies": { - "System.Collections.Immutable": "9.0.4" - } + "contentHash": "qeJNsMmZPc/Lieg0Md+D4F6LoLcxV3b9QsUNmBRXc2ZVOkMbAcwuO9l2jbQFv3n+fLiHJilN8v6i5aJNivjrCQ==" }, "System.Runtime.CompilerServices.Unsafe": { "type": "Direct", @@ -77,8 +74,11 @@ "System.Collections.Immutable": { "type": "CentralTransitive", "requested": "[9.0.4, )", - "resolved": "9.0.4", - "contentHash": "wfm2NgK22MmBe5qJjp52qzpkeDZKb4l9LbdubhZSehY1z4LS+lld6R+B+UQNb2AZRHu/QJlHxEUcRst5hIEejg==" + "resolved": "6.0.0", + "contentHash": "l4zZJ1WU2hqpQQHXz1rvC3etVZN+2DLmQMO79FhOTZHMn8tDRr+WU287sbomD0BETlmKDn0ygUgVy9k5xkkJdA==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } } } } From f673b7caa2c110a71ef20ea72b33946469f8f123 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 25 May 2025 05:52:48 +0200 Subject: [PATCH 03/14] Add support for using different editions of .NET in unit tests --- .../Helpers/RoslynToolset.cs | 103 ++++++++++--- .../Helpers/Tester.VB.cs | 24 +-- .../Helpers/Tester.cs | 139 ++++++++++++------ .../PrettyTestRunner.cs | 6 - .../TestCases/ILPretty/Issue1325.il | 2 +- .../TestCases/Pretty/MetadataAttributes.cs | 8 +- .../TestCases/Pretty/Switch.cs | 4 +- .../TestCases/Pretty/WellKnownConstants.cs | 2 +- .../TypeSystem/TypeSystemLoaderTests.cs | 4 +- ILSpy-tests | 2 +- 10 files changed, 201 insertions(+), 93 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Helpers/RoslynToolset.cs b/ICSharpCode.Decompiler.Tests/Helpers/RoslynToolset.cs index cf53163d6..fb3fa757e 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/RoslynToolset.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/RoslynToolset.cs @@ -18,12 +18,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using ICSharpCode.Decompiler.Util; +using ICSharpCode.Decompiler.Metadata; using NuGet.Common; using NuGet.Packaging; @@ -31,6 +32,8 @@ using NuGet.Protocol; using NuGet.Protocol.Core.Types; using NuGet.Versioning; +using NUnit.Framework; + namespace ICSharpCode.Decompiler.Tests.Helpers { abstract class AbstractToolset @@ -50,29 +53,45 @@ namespace ICSharpCode.Decompiler.Tests.Helpers protected async Task FetchPackage(string packageName, string version, string sourcePath, string outputPath) { + if (!Directory.Exists(Path.Combine(Roundtrip.RoundtripAssembly.TestDir, "nuget"))) + Assert.Fail("No nuget cache found!"); + ILogger logger = NullLogger.Instance; CancellationToken cancellationToken = CancellationToken.None; - using MemoryStream packageStream = new MemoryStream(); - - await resource.CopyNupkgToStreamAsync( - packageName, - NuGetVersion.Parse(version), - packageStream, - cache, - logger, - cancellationToken).ConfigureAwait(false); - - using PackageArchiveReader packageReader = new PackageArchiveReader(packageStream); - NuspecReader nuspecReader = await packageReader.GetNuspecReaderAsync(cancellationToken).ConfigureAwait(false); - - var files = (await packageReader.GetFilesAsync(cancellationToken).ConfigureAwait(false)).ToArray(); - files = files.Where(f => f.StartsWith(sourcePath, StringComparison.OrdinalIgnoreCase)).ToArray(); - await packageReader.CopyFilesAsync(outputPath, files, - (sourceFile, targetPath, fileStream) => { - fileStream.CopyToFile(targetPath); - return targetPath; - }, - logger, cancellationToken).ConfigureAwait(false); + string pathToPackage = Path.Combine(Roundtrip.RoundtripAssembly.TestDir, "nuget", $"{packageName}-{version}.nupkg"); + Stream packageStream; + if (File.Exists(pathToPackage)) + { + packageStream = File.OpenRead(pathToPackage); + } + else + { + packageStream = new MemoryStream(); + + await resource.CopyNupkgToStreamAsync( + packageName, + NuGetVersion.Parse(version), + packageStream, + cache, + logger, + cancellationToken).ConfigureAwait(false); + + packageStream.Position = 0; + } + using (packageStream) + { + using PackageArchiveReader packageReader = new PackageArchiveReader(packageStream); + NuspecReader nuspecReader = await packageReader.GetNuspecReaderAsync(cancellationToken).ConfigureAwait(false); + + var files = (await packageReader.GetFilesAsync(cancellationToken).ConfigureAwait(false)).ToArray(); + files = files.Where(f => f.StartsWith(sourcePath, StringComparison.OrdinalIgnoreCase)).ToArray(); + await packageReader.CopyFilesAsync(outputPath, files, + (sourceFile, targetPath, fileStream) => { + fileStream.CopyToFile(targetPath); + return targetPath; + }, + logger, cancellationToken).ConfigureAwait(false); + } } } @@ -145,4 +164,44 @@ namespace ICSharpCode.Decompiler.Tests.Helpers public string GetVsWhere() => vswherePath; } + + class RefAssembliesToolset : AbstractToolset + { + readonly Dictionary installedFrameworks = new Dictionary { + { "legacy", Path.Combine(Roundtrip.RoundtripAssembly.TestDir, "dotnet", "legacy") }, + { "2.2.0", Path.Combine(Roundtrip.RoundtripAssembly.TestDir, "dotnet", "netcore-2.2") }, + }; + + public RefAssembliesToolset() + : base(Path.Combine(AppContext.BaseDirectory, "netfx")) + { + } + + public async Task Fetch(string version, string packageName = "Microsoft.NETCore.App.Ref", string sourcePath = "ref/net5.0") + { + string path = Path.Combine(baseDir, version, sourcePath); + if (!Directory.Exists(path)) + { + await FetchPackage(packageName, version, sourcePath, Path.Combine(baseDir, version)).ConfigureAwait(false); + } + + installedFrameworks.Add(RoslynToolset.SanitizeVersion(version), path); + } + + internal string GetPath(string targetFramework) + { + var (id, version) = UniversalAssemblyResolver.ParseTargetFramework(targetFramework); + string path; + if (id == TargetFrameworkIdentifier.NETFramework) + { + path = installedFrameworks["legacy"]; + } + else + { + path = installedFrameworks[version.ToString(3)]; + } + Debug.Assert(Path.Exists(path)); + return path; + } + } } diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs index 6b8e403e3..c40f267bc 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs @@ -16,12 +16,9 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; -using System.CodeDom.Compiler; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -49,27 +46,30 @@ namespace ICSharpCode.Decompiler.Tests.Helpers CompilerResults results = new CompilerResults(); results.PathToAssembly = outputFileName; - var (roslynVersion, languageVersion) = (flags & CompilerOptions.UseRoslynMask) switch { - 0 => ("legacy", "11"), - CompilerOptions.UseRoslyn1_3_2 => ("1.3.2", "14"), - CompilerOptions.UseRoslyn2_10_0 => ("2.10.0", "latest"), - CompilerOptions.UseRoslyn3_11_0 => ("3.11.0", "latest"), - _ => (roslynLatestVersion, flags.HasFlag(CompilerOptions.Preview) ? "preview" : "latest") + bool targetNet40 = (flags & CompilerOptions.TargetNet40) != 0; + + var (roslynVersion, languageVersion, targetFramework) = (flags & CompilerOptions.UseRoslynMask) switch { + 0 => ("legacy", "11", null), + CompilerOptions.UseRoslyn1_3_2 => ("1.3.2", "14", null), + CompilerOptions.UseRoslyn2_10_0 => ("2.10.0", "latest", targetNet40 ? null : ".NETCoreApp,Version=v2.2"), + CompilerOptions.UseRoslyn3_11_0 => ("3.11.0", "latest", targetNet40 ? null : ".NETCoreApp,Version=v5.0"), + _ => (roslynLatestVersion, flags.HasFlag(CompilerOptions.Preview) ? "preview" : "latest", targetNet40 ? null : ".NETCoreApp,Version=v10.0") }; var vbcPath = roslynToolset.GetVBCompiler(roslynVersion); IEnumerable references; string libPath; - if ((flags & CompilerOptions.UseRoslynMask) != 0 && (flags & CompilerOptions.TargetNet40) == 0) + if ((flags & CompilerOptions.UseRoslynMask) != 0 && targetFramework != null) { + var coreRefAsmPath = RefAssembliesToolset.GetPath(targetFramework); references = coreDefaultReferences.Select(r => "-r:\"" + r + "\""); libPath = coreRefAsmPath; } else { references = defaultReferences.Select(r => "-r:\"" + r + "\""); - libPath = RefAsmPath; + libPath = RefAssembliesToolset.GetPath("legacy"); } if (flags.HasFlag(CompilerOptions.ReferenceVisualBasic)) { @@ -121,7 +121,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers var command = Cli.Wrap(vbcPath) .WithArguments($"{otherOptions}-libpath:\"{libPath}\" {string.Join(" ", references)} -out:\"{Path.GetFullPath(results.PathToAssembly)}\" {string.Join(" ", sourceFileNames.Select(fn => '"' + Path.GetFullPath(fn) + '"'))}") .WithValidation(CommandResultValidation.None); - Console.WriteLine($"\"{command.TargetFilePath}\" {command.Arguments}"); + //Console.WriteLine($"\"{command.TargetFilePath}\" {command.Arguments}"); var result = await command.ExecuteBufferedAsync().ConfigureAwait(false); diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index d1c00d9cc..00b44f989 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -99,6 +99,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers static readonly string roslynLatestVersion; static readonly RoslynToolset roslynToolset; static readonly VsWhereToolset vswhereToolset; + internal static readonly RefAssembliesToolset RefAssembliesToolset; static Tester() { @@ -120,6 +121,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers roslynToolset = new RoslynToolset(); vswhereToolset = new VsWhereToolset(); + RefAssembliesToolset = new RefAssembliesToolset(); } internal static async Task Initialize() @@ -130,6 +132,9 @@ namespace ICSharpCode.Decompiler.Tests.Helpers await roslynToolset.Fetch(roslynLatestVersion).ConfigureAwait(false); await vswhereToolset.Fetch().ConfigureAwait(false); + await RefAssembliesToolset.Fetch("5.0.0", sourcePath: "ref/net5.0").ConfigureAwait(false); + await RefAssembliesToolset.Fetch("10.0.0-preview.4.25258.110", sourcePath: "ref/net10.0").ConfigureAwait(false); + #if DEBUG await BuildTestRunner("win-x86", "Debug").ConfigureAwait(false); @@ -276,21 +281,15 @@ namespace ICSharpCode.Decompiler.Tests.Helpers return Regex.Replace(il, @"'\{[0-9A-F-]+\}'", "''"); } - static readonly string coreRefAsmPath = new DotNetCorePathFinder(TargetFrameworkIdentifier.NET, - new Version(10, 0), "Microsoft.NETCore.App") - .GetReferenceAssemblyPath(".NETCoreApp,Version=v10.0"); - - public static readonly string RefAsmPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), - @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2"); - static readonly string[] defaultReferences = new[] { "System.dll", "System.Core.dll", + "System.Runtime.dll", "System.Xml.dll", "Microsoft.CSharp.dll" }; - static readonly string[] coreDefaultReferences = new[] + static readonly string[] core220DefaultReferences = new[] { "netstandard.dll", "mscorlib.dll", @@ -303,9 +302,12 @@ namespace ICSharpCode.Decompiler.Tests.Helpers "System.Linq.Queryable.dll", "System.IO.FileSystem.Watcher.dll", "System.Memory.dll", + "System.Private.CoreLib.dll", + "System.Private.Xml.dll", "System.Threading.dll", "System.Threading.Thread.dll", "System.Runtime.dll", + "System.Runtime.Extensions.dll", "System.Runtime.InteropServices.dll", "System.Xml.dll", "System.Xml.ReaderWriter.dll", @@ -314,19 +316,45 @@ namespace ICSharpCode.Decompiler.Tests.Helpers "Microsoft.VisualBasic.dll", }; - const string targetFrameworkAttributeSnippet = @" - -[assembly: System.Runtime.Versioning.TargetFramework("".NETCoreApp,Version=v10.0"", FrameworkDisplayName = """")] - -"; + static readonly string[] coreDefaultReferences = new[] + { + "netstandard.dll", + "mscorlib.dll", + "System.dll", + "System.Collections.dll", + "System.Console.dll", + "System.Core.dll", + "System.Linq.dll", + "System.Linq.Expressions.dll", + "System.Linq.Queryable.dll", + "System.IO.FileSystem.Watcher.dll", + "System.Memory.dll", + "System.Threading.dll", + "System.Threading.Thread.dll", + "System.Runtime.dll", + "System.Runtime.InteropServices.dll", + "System.Xml.dll", + "System.Xml.ReaderWriter.dll", + "System.ValueTuple.dll", + "Microsoft.CSharp.dll", + "Microsoft.VisualBasic.dll", + }; - static readonly Lazy targetFrameworkAttributeSnippetFile = new Lazy(GetTargetFrameworkAttributeSnippetFile); + static readonly Dictionary> targetFrameworkAttributeSnippetFiles = new() { + { ".NETCoreApp,Version=v10.0", new Lazy(() => GetTargetFrameworkAttributeSnippetFile(".NETCoreApp,Version=v10.0")) }, + { ".NETCoreApp,Version=v5.0", new Lazy(() => GetTargetFrameworkAttributeSnippetFile(".NETCoreApp,Version=v5.0")) }, + { ".NETCoreApp,Version=v2.2", new Lazy(() => GetTargetFrameworkAttributeSnippetFile(".NETCoreApp,Version=v2.2")) }, + }; - static string GetTargetFrameworkAttributeSnippetFile() + static string GetTargetFrameworkAttributeSnippetFile(string targetFrameworkMoniker) { // 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, targetFrameworkAttributeSnippet); + File.WriteAllText(tempFile, $@" + +[assembly: System.Runtime.Versioning.TargetFramework(""{targetFrameworkMoniker}"")] + +"); return tempFile; } @@ -389,9 +417,6 @@ namespace System.Runtime.CompilerServices if (!flags.HasFlag(CompilerOptions.TargetNet40)) { preprocessorSymbols.Add("NETCORE"); - preprocessorSymbols.Add("NET60"); - preprocessorSymbols.Add("NET70"); - preprocessorSymbols.Add("NET80"); } preprocessorSymbols.Add("ROSLYN"); preprocessorSymbols.Add("CS60"); @@ -411,6 +436,10 @@ namespace System.Runtime.CompilerServices if (flags.HasFlag(CompilerOptions.UseRoslyn3_11_0) || flags.HasFlag(CompilerOptions.UseRoslynLatest)) { + if (!flags.HasFlag(CompilerOptions.TargetNet40)) + { + preprocessorSymbols.Add("NET50"); + } preprocessorSymbols.Add("ROSLYN3"); preprocessorSymbols.Add("CS80"); preprocessorSymbols.Add("CS90"); @@ -418,6 +447,14 @@ namespace System.Runtime.CompilerServices } if (flags.HasFlag(CompilerOptions.UseRoslynLatest)) { + if (!flags.HasFlag(CompilerOptions.TargetNet40)) + { + preprocessorSymbols.Add("NET60"); + preprocessorSymbols.Add("NET70"); + preprocessorSymbols.Add("NET80"); + preprocessorSymbols.Add("NET90"); + preprocessorSymbols.Add("NET100"); + } preprocessorSymbols.Add("ROSLYN4"); preprocessorSymbols.Add("CS100"); preprocessorSymbols.Add("CS110"); @@ -453,10 +490,6 @@ namespace System.Runtime.CompilerServices } bool targetNet40 = (flags & CompilerOptions.TargetNet40) != 0; bool useRoslyn = (flags & CompilerOptions.UseRoslynMask) != 0; - if (useRoslyn && !targetNet40) - { - sourceFileNames.Add(targetFrameworkAttributeSnippetFile.Value); - } if (targetNet40) { @@ -470,36 +503,58 @@ namespace System.Runtime.CompilerServices CompilerResults results = new CompilerResults(); results.PathToAssembly = outputFileName; - var (roslynVersion, languageVersion) = (flags & CompilerOptions.UseRoslynMask) switch { - 0 => ("legacy", "5"), - CompilerOptions.UseRoslyn1_3_2 => ("1.3.2", "6"), - CompilerOptions.UseRoslyn2_10_0 => ("2.10.0", "latest"), - CompilerOptions.UseRoslyn3_11_0 => ("3.11.0", "latest"), - _ => (roslynLatestVersion, flags.HasFlag(CompilerOptions.Preview) ? "preview" : "latest") + var (roslynVersion, languageVersion, targetFramework) = (flags & CompilerOptions.UseRoslynMask) switch { + 0 => ("legacy", "5", null), + CompilerOptions.UseRoslyn1_3_2 => ("1.3.2", "6", null), + CompilerOptions.UseRoslyn2_10_0 => ("2.10.0", "latest", targetNet40 ? null : ".NETCoreApp,Version=v2.2"), + CompilerOptions.UseRoslyn3_11_0 => ("3.11.0", "latest", targetNet40 ? null : ".NETCoreApp,Version=v5.0"), + _ => (roslynLatestVersion, flags.HasFlag(CompilerOptions.Preview) ? "preview" : "latest", targetNet40 ? null : ".NETCoreApp,Version=v10.0") }; var cscPath = roslynToolset.GetCSharpCompiler(roslynVersion); - string libPath; + string libPath, refAsmPath; IEnumerable references; - if (useRoslyn && !targetNet40) + if (useRoslyn && targetFramework != null) { - libPath = "\"" + coreRefAsmPath + "\""; - references = coreDefaultReferences.Select(r => "-r:\"" + Path.Combine(coreRefAsmPath, r) + "\""); + refAsmPath = RefAssembliesToolset.GetPath(targetFramework); + if (targetFramework == ".NETCoreApp,Version=v2.2") + { + references = core220DefaultReferences; + if (flags.HasFlag(CompilerOptions.ReferenceVisualBasic)) + { + references = references.Append("Microsoft.VisualBasic.dll"); + } + } + else + { + references = coreDefaultReferences; + if (flags.HasFlag(CompilerOptions.ReferenceVisualBasic)) + { + references = references.Append("Microsoft.VisualBasic.dll"); + references = references.Append("Microsoft.VisualBasic.Core.dll"); + } + } + libPath = "\"" + refAsmPath + "\""; + sourceFileNames.Add(targetFrameworkAttributeSnippetFiles[targetFramework].Value); } else { - libPath = "\"" + RefAsmPath + "\",\"" + Path.Combine(RefAsmPath, "Facades") + "\""; - references = defaultReferences.Select(r => "-r:\"" + Path.Combine(RefAsmPath, r) + "\""); - } - if (flags.HasFlag(CompilerOptions.ReferenceVisualBasic)) - { - references = references.Concat(new[] { "-r:\"Microsoft.VisualBasic.dll\"" }); + refAsmPath = RefAssembliesToolset.GetPath("legacy"); + libPath = "\"" + refAsmPath + "\""; + references = defaultReferences; + if (flags.HasFlag(CompilerOptions.ReferenceVisualBasic)) + { + references = references.Append("Microsoft.VisualBasic.dll"); + } } if (useRoslyn && !targetNet40 && flags.HasFlag(CompilerOptions.ReferenceUnsafe)) { - references = references.Concat(new[] { "-r:\"System.Runtime.CompilerServices.Unsafe.dll\"" }); + references = references.Append("System.Runtime.CompilerServices.Unsafe.dll"); } + + references = references.Select(r => "-r:\"" + Path.Combine(refAsmPath, r) + "\""); + string otherOptions = $"-nologo -noconfig " + $"-langversion:{languageVersion} " + $"-unsafe -o{(flags.HasFlag(CompilerOptions.Optimize) ? "+ " : "- ")}"; @@ -685,7 +740,7 @@ namespace System.Runtime.CompilerServices } var compilation = CSharpCompilation.Create(Path.GetFileNameWithoutExtension(assemblyName), - syntaxTrees, coreDefaultReferences.Select(r => MetadataReference.CreateFromFile(Path.Combine(coreRefAsmPath, r))), + syntaxTrees, coreDefaultReferences.Select(r => MetadataReference.CreateFromFile(Path.Combine(RefAssembliesToolset.GetPath(".NETCoreApp,Version=v10.0"), r))), new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary, platform: Platform.AnyCpu, @@ -767,7 +822,7 @@ namespace System.Runtime.CompilerServices string targetFramework = module.Metadata.DetectTargetFrameworkId(); var resolver = new UniversalAssemblyResolver(assemblyFileName, false, targetFramework, null, PEStreamOptions.PrefetchMetadata); - resolver.AddSearchDirectory(targetFramework.Contains(".NETFramework") ? RefAsmPath : coreRefAsmPath); + resolver.AddSearchDirectory(RefAssembliesToolset.GetPath(targetFramework)); var typeSystem = new DecompilerTypeSystem(module, resolver, settings); CSharpDecompiler decompiler = new CSharpDecompiler(typeSystem, settings); decompiler.AstTransforms.Insert(0, new RemoveEmbeddedAttributes()); diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 12020c28b..ae18373fa 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -59,8 +59,6 @@ namespace ICSharpCode.Decompiler.Tests static readonly CompilerOptions[] roslynOnlyWithNet40Options = { - CompilerOptions.UseRoslyn1_3_2 | CompilerOptions.TargetNet40, - CompilerOptions.Optimize | CompilerOptions.UseRoslyn1_3_2 | CompilerOptions.TargetNet40, CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, CompilerOptions.Optimize | CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.TargetNet40, @@ -145,8 +143,6 @@ namespace ICSharpCode.Decompiler.Tests { CompilerOptions.None, CompilerOptions.Optimize, - CompilerOptions.UseRoslyn1_3_2 | CompilerOptions.TargetNet40, - CompilerOptions.Optimize | CompilerOptions.UseRoslyn1_3_2 | CompilerOptions.TargetNet40, CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, CompilerOptions.Optimize | CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.TargetNet40, @@ -167,8 +163,6 @@ namespace ICSharpCode.Decompiler.Tests { CompilerOptions.None, CompilerOptions.Optimize, - CompilerOptions.UseRoslyn1_3_2 | CompilerOptions.TargetNet40, - CompilerOptions.Optimize | CompilerOptions.UseRoslyn1_3_2 | CompilerOptions.TargetNet40, CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, CompilerOptions.Optimize | CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.TargetNet40, diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.il index 789dfd710..129422762 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.il +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.il @@ -31,7 +31,7 @@ // .custom instance void [System.Runtime]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 ) .custom instance void [System.Runtime]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 18 2E 4E 45 54 43 6F 72 65 41 70 70 2C 56 // ....NETCoreApp,V - 65 72 73 69 6F 6E 3D 76 32 2E 31 01 00 54 0E 14 // ersion=v2.1..T.. + 65 72 73 69 6F 6E 3D 76 32 2E 32 01 00 54 0E 14 // ersion=v2.2..T.. 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61 79 // FrameworkDisplay 4E 61 6D 65 00 ) // Name. .custom instance void [System.Runtime]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 05 44 65 62 75 67 00 00 ) // ...Debug.. diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MetadataAttributes.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MetadataAttributes.cs index a1a8e258c..0ba3e51f6 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MetadataAttributes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/MetadataAttributes.cs @@ -9,7 +9,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { [MethodImpl(MethodImplOptions.AggressiveInlining)] public extern void A(); -#if NETCORE +#if !NET40 && ROSLYN3 [MethodImpl(MethodImplOptions.AggressiveOptimization)] public extern void B(); #endif @@ -29,7 +29,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public extern void J(); [MethodImpl(MethodImplOptions.AggressiveInlining, MethodCodeType = MethodCodeType.Native)] public extern void A1(); -#if NETCORE +#if !NET40 && ROSLYN3 [MethodImpl(MethodImplOptions.AggressiveOptimization, MethodCodeType = MethodCodeType.Native)] public extern void B1(); #endif @@ -49,7 +49,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public extern void J1(); [MethodImpl(MethodImplOptions.AggressiveInlining, MethodCodeType = MethodCodeType.OPTIL)] public extern void A2(); -#if NETCORE +#if !NET40 && ROSLYN3 [MethodImpl(MethodImplOptions.AggressiveOptimization, MethodCodeType = MethodCodeType.OPTIL)] public extern void B2(); #endif @@ -69,7 +69,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public extern void J2(); [MethodImpl(MethodImplOptions.AggressiveInlining, MethodCodeType = MethodCodeType.OPTIL)] public extern void A3(); -#if NETCORE +#if !NET40 && ROSLYN3 [MethodImpl(MethodImplOptions.AggressiveOptimization, MethodCodeType = MethodCodeType.Runtime)] public extern void B3(); #endif diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs index f5647187f..f8ca658ed 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs @@ -1624,7 +1624,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static bool DoNotRemoveAssignmentBeforeSwitch(string x, out ConsoleKey key) { -#if NET40 || !ROSLYN +#if NET40 || !ROSLYN4 key = (ConsoleKey)0; #else key = ConsoleKey.None; @@ -1641,7 +1641,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty key = ConsoleKey.C; break; } -#if NET40 || !ROSLYN +#if NET40 || !ROSLYN4 return key != (ConsoleKey)0; #else return key != ConsoleKey.None; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs index 9170a8d89..109909388 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs @@ -83,7 +83,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public const float Float_Tenth = 0.1f; public const double Double_Tenth = 0.1; -#if ROSLYN && !NET40 +#if ROSLYN2 && !NET40 public const float Float_PI = MathF.PI; public const float Float_HalfOfPI = MathF.PI / 2f; public const float Float_QuarterOfPI = MathF.PI / 4f; diff --git a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs index 2059f6c42..94436f4d6 100644 --- a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs +++ b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs @@ -47,12 +47,12 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem static readonly Lazy mscorlib = new Lazy( delegate { - return LoadAssembly(Path.Combine(Helpers.Tester.RefAsmPath, "mscorlib.dll")); + return LoadAssembly(Path.Combine(Helpers.Tester.RefAssembliesToolset.GetPath("legacy"), "mscorlib.dll")); }); static readonly Lazy systemCore = new Lazy( delegate { - return LoadAssembly(Path.Combine(Helpers.Tester.RefAsmPath, "System.Core.dll")); + return LoadAssembly(Path.Combine(Helpers.Tester.RefAssembliesToolset.GetPath("legacy"), "System.Core.dll")); }); static readonly Lazy testAssembly = new Lazy( diff --git a/ILSpy-tests b/ILSpy-tests index 6f8860e42..dd5081d55 160000 --- a/ILSpy-tests +++ b/ILSpy-tests @@ -1 +1 @@ -Subproject commit 6f8860e420b54bdfd726ec3c58a4d178416f9156 +Subproject commit dd5081d55372c57744874042f60e7c23ff69f5f6 From 25eb4910d19469ebc6ccf548b15986dfa4219686 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 29 May 2025 10:25:44 +0200 Subject: [PATCH 04/14] update submodule --- .gitmodules | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitmodules b/.gitmodules index 968e288de..007896d14 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "ILSpy-tests"] path = ILSpy-tests url = https://github.com/icsharpcode/ILSpy-tests + branch = master From 2461f520abfd2615552ed522942a5168ac08eb3b Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 26 Apr 2025 18:22:19 +0200 Subject: [PATCH 05/14] Move LocalFunctionDecompiler.GetStatement to Block.GetContainingStatement --- .../IL/ControlFlow/AwaitInFinallyTransform.cs | 4 +--- ICSharpCode.Decompiler/IL/Instructions/Block.cs | 16 ++++++++++++++++ .../Transforms/DetectCatchWhenConditionBlocks.cs | 5 +---- .../IL/Transforms/ExpressionTransforms.cs | 15 ++------------- .../IL/Transforms/LocalFunctionDecompiler.cs | 13 +------------ .../IL/Transforms/TransformDisplayClassUsage.cs | 2 +- 6 files changed, 22 insertions(+), 33 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AwaitInFinallyTransform.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AwaitInFinallyTransform.cs index 7cccce8bd..689b2860d 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/AwaitInFinallyTransform.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/AwaitInFinallyTransform.cs @@ -16,12 +16,10 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.TypeSystem; @@ -109,7 +107,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow if (objectVariable.LoadCount != 1 || objectVariable.StoreCount > 2) continue; - var beforeExceptionCaptureBlock = (Block)LocalFunctionDecompiler.GetStatement(objectVariable.LoadInstructions[0])?.Parent; + var beforeExceptionCaptureBlock = Block.FindClosestBlock(objectVariable.LoadInstructions[0]); if (beforeExceptionCaptureBlock == null) continue; diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs index 479a8f207..06ea213f0 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs @@ -383,6 +383,22 @@ namespace ICSharpCode.Decompiler.IL return null; } + /// + /// Gets the closest ancestor that is child of a control-flow (top-level) Block. + /// Returns null, if the instruction is not a descendant of a Block. + /// + public static ILInstruction? GetContainingStatement(ILInstruction inst) + { + var curr = inst; + while (curr != null) + { + if (curr.Parent is Block { Kind: BlockKind.ControlFlow }) + return curr; + curr = curr.Parent; + } + return null; + } + public bool MatchInlineAssignBlock([NotNullWhen(true)] out CallInstruction? call, [NotNullWhen(true)] out ILInstruction? value) { call = null; diff --git a/ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs b/ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs index f68c6d076..75b3860d9 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs @@ -16,9 +16,6 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.TypeSystem; @@ -107,7 +104,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms // We are only interested in store "statements" copying the exception variable // without modifying it. - var statement = LocalFunctionDecompiler.GetStatement(load); + var statement = Block.GetContainingStatement(load); if (!(statement is StLoc stloc)) { i++; diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 7d36fb9e2..f73f624f1 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -312,25 +312,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms protected internal override void VisitNewObj(NewObj inst) { - Block block; if (TransformSpanTCtorContainingStackAlloc(inst, out ILInstruction locallocSpan)) { context.Step("new Span(stackalloc) -> stackalloc Span", inst); inst.ReplaceWith(locallocSpan); - block = null; - ILInstruction stmt = locallocSpan; - while (stmt.Parent != null) - { - if (stmt.Parent is Block b) - { - block = b; - break; - } - stmt = stmt.Parent; - } + ILInstruction stmt = Block.GetContainingStatement(locallocSpan); // Special case to eliminate extra store if (stmt.GetNextSibling() is StLoc storeStmt && storeStmt.Value is LdLoc) - ILInlining.InlineIfPossible(block, stmt.ChildIndex, context); + ILInlining.InlineIfPossible((Block)stmt.Parent, stmt.ChildIndex, context); return; } if (TransformArrayInitializers.TransformSpanTArrayInitialization(inst, context, out var replacement)) diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index c377238c1..4b73758b5 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -575,17 +575,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms && TransformDisplayClassUsage.IsPotentialClosure(context.CurrentTypeDefinition, type); } - internal static ILInstruction GetStatement(ILInstruction inst) - { - while (inst.Parent != null) - { - if (inst.Parent is Block b && b.Kind == BlockKind.ControlFlow) - return inst; - inst = inst.Parent; - } - return inst; - } - LocalFunctionMethod ReduceToLocalFunction(IMethod method, int typeParametersToRemove) { int parametersToRemove = 0; @@ -747,7 +736,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (variable.Kind == VariableKind.Parameter) return null; if (type.Kind == TypeKind.Struct) - return GetStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First()); + return Block.GetContainingStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First()); else return (StLoc)variable.StoreInstructions[0]; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs index c433c3b81..103c77d79 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -604,7 +604,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (context.Settings.LocalFunctions && closureType?.Kind == TypeKind.Struct && variable.UsesInitialValue && IsPotentialClosure(context, closureType)) { - initializer = LocalFunctionDecompiler.GetStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First()); + initializer = Block.GetContainingStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First()); return true; } return false; From 34490587d9013d0feea26df21ffc7fbe8a196466 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 29 May 2025 13:40:49 +0200 Subject: [PATCH 06/14] Hide compiler-generated InlineArray types. --- ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs | 2 ++ ILSpy-tests | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 5eba14b77..ceec1103e 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -316,6 +316,8 @@ namespace ICSharpCode.Decompiler.CSharp return true; if (settings.FixedBuffers && name.StartsWith("<", StringComparison.Ordinal) && name.Contains("__FixedBuffer")) return true; + if (settings.InlineArrays && name.StartsWith("<>y__InlineArray", StringComparison.Ordinal) && name.EndsWith("`1")) + return true; } else if (type.IsCompilerGenerated(metadata)) { diff --git a/ILSpy-tests b/ILSpy-tests index dd5081d55..6cc4bb294 160000 --- a/ILSpy-tests +++ b/ILSpy-tests @@ -1 +1 @@ -Subproject commit dd5081d55372c57744874042f60e7c23ff69f5f6 +Subproject commit 6cc4bb2942a1c6a134510ad4d916f77604d37b51 From 586c04d0817e879ee4aeabefb5037b457087c4d1 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 07/14] 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 类型 From 27e4bc578b0783b2d9045fdbb1299e1c86bddce7 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 20 Jun 2025 22:51:10 +0200 Subject: [PATCH 08/14] ref structs cannot be boxed --- .../TestCases/Correctness/Conversions.cs | 4 ++-- ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/Conversions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/Conversions.cs index 59e94ed4b..040010e8f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/Conversions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/Conversions.cs @@ -112,7 +112,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness Console.WriteLine(ReadZeroTerminatedString("Hello World!".Length)); C1.Test(); -#if ROSLYN2 && !NET40 +#if CS120 && !NET40 C3.Run(); #endif } @@ -204,7 +204,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness } } -#if ROSLYN2 && !NET40 +#if CS120 && !NET40 class C3 { [InlineArray(4)] struct MyArray { private int elem; } diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs index 6acbeaa49..bc857c9f1 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs @@ -779,7 +779,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver { // C# 4.0 spec: §6.1.7 fromType = NullableType.GetUnderlyingType(fromType); - if (fromType.IsReferenceType == false && toType.IsReferenceType == true) + if (fromType.IsReferenceType == false && !fromType.IsByRefLike && toType.IsReferenceType == true) return IsSubtypeOf(fromType, toType, 0); else return false; From fecb10f109be74a4789d843c91b1d37e4e8c3b79 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 22 Jun 2025 08:21:30 +0200 Subject: [PATCH 09/14] Generalize ILInlining special cases for in arguments of ROS ctors. --- .../IL/Transforms/ILInlining.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index af52fd58b..06565aebe 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -337,20 +337,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms { return true; } - else if (IsPassedToReadOnlySpanOfCharCtor(loadInst)) + else if (IsPassedToReadOnlySpanCtor(loadInst)) { // Always inlining is possible here, because it's an 'in' or 'ref readonly' parameter // and the C# compiler allows calling it with an rvalue, even though that might produce // a warning. Note that we don't need to check the expression classification, because // expressionBuilder.VisitAddressOf will handle creating the copy for us. // This is necessary, because there are compiler-generated uses of this ctor when - // concatenating a string to a char and our following transforms assume the char is - // already inlined. + // passing a single-element array to a params ROS parameter and our following transforms + // assume the value is already inlined. return true; } - if (IsPassedToInlineArrayAsSpan(loadInst)) + else if (IsPassedToInlineArrayAsSpan(loadInst)) { - // Inlining is not allowed + // Inlining is not allowed: + // .InlineArrayAsReadOnlySpan(GetInlineArray()) is invalid C# code return false; } else if (IsPassedToInParameter(loadInst)) @@ -491,13 +492,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms return call.GetParameter(ldloca.ChildIndex)?.ReferenceKind is ReferenceKind.In; } - static bool IsPassedToReadOnlySpanOfCharCtor(LdLoca ldloca) + static bool IsPassedToReadOnlySpanCtor(LdLoca ldloca) { if (ldloca.Parent is not NewObj call) { return false; } - return IsReadOnlySpanCharCtor(call.Method); + var method = call.Method; + return method.IsConstructor + && method.Parameters.Count == 1 + && method.DeclaringType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT); } internal static bool IsReadOnlySpanCharCtor(IMethod method) From d9bc4d19a23622150313bfb2f6566a22bc80082f Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 22 Jun 2025 08:23:47 +0200 Subject: [PATCH 10/14] Add support for newobj ROS(in arg) to TransformParamsArgument. --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 85 +++++++++++--------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 058eff4e3..f57051e05 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1026,25 +1026,14 @@ namespace ICSharpCode.Decompiler.CSharp } private bool TransformParamsArgument(ExpectedTargetDetails expectedTargetDetails, ResolveResult targetResolveResult, - IMethod method, IParameter parameter, TranslatedExpression arg, ref List expectedParameters, + IMethod method, IParameter parameter, TranslatedExpression paramsArgument, ref List expectedParameters, ref List arguments) { - if (CheckArgument(out int length, out IType elementType)) + var expressionBuilder = this.expressionBuilder; + if (ExtractArguments(out IType elementType, out var expandedParameters, out var expandedArguments)) { - var expandedParameters = new List(expectedParameters); - var expandedArguments = new List(arguments); - if (length > 0) - { - var arrayElements = ((ArrayCreateExpression)arg.Expression).Initializer.Elements.ToArray(); - for (int j = 0; j < length; j++) - { - expandedParameters.Add(new DefaultParameter(elementType, parameter.Name + j)); - if (j < arrayElements.Length) - expandedArguments.Add(new TranslatedExpression(arrayElements[j])); - else - expandedArguments.Add(expressionBuilder.GetDefaultValueExpression(elementType).WithoutILInstruction()); - } - } + expandedParameters.InsertRange(0, expectedParameters); + expandedArguments.InsertRange(0, arguments); if (IsUnambiguousCall(expectedTargetDetails, method, targetResolveResult, Empty.Array, expandedArguments.SelectArray(a => a.ResolveResult), argumentNames: null, firstOptionalArgumentIndex: -1, out _, @@ -1057,30 +1046,52 @@ namespace ICSharpCode.Decompiler.CSharp } return false; - bool CheckArgument(out int len, out IType t) + bool ExtractArguments(out IType elementType, out List parameters, out List arguments) { - len = 0; - t = null; - if (arg.ResolveResult is CSharpInvocationResolveResult csirr && - csirr.Arguments.Count == 0 && csirr.Member is IMethod emptyMethod && - emptyMethod.IsStatic && - "System.Array.Empty" == emptyMethod.FullName && - emptyMethod.TypeArguments.Count == 1) + elementType = null; + parameters = null; + arguments = null; + switch (paramsArgument.ResolveResult) { - t = emptyMethod.TypeArguments[0]; - return true; - } - - if (arg.ResolveResult is ArrayCreateResolveResult acrr && - acrr.SizeArguments.Count == 1 && - acrr.SizeArguments[0].IsCompileTimeConstant && - acrr.SizeArguments[0].ConstantValue is int l) - { - len = l; - t = ((ArrayType)acrr.Type).ElementType; - return true; + case CSharpInvocationResolveResult { Member: IMethod method, Arguments: var args }: + // match System.Array.Empty() + if (args is [] && method is { IsStatic: true, FullName: "System.Array.Empty", TypeArguments: [var type] }) + { + elementType = type; + arguments = new(); + parameters = new(); + return true; + } + // match System.ReadOnlySpan..ctor(ref readonly T) + if (paramsArgument.Expression is ObjectCreateExpression oce + && method is { + IsConstructor: true, + Parameters: [{ ReferenceKind: ReferenceKind.RefReadOnly, Type: ByReferenceType { ElementType: var paramType } }], + DeclaringType: { TypeArguments: [var type2] } declaringType + } + && declaringType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT) + && paramType.Equals(type2)) + { + elementType = type2; + arguments = new() { new TranslatedExpression(oce.Arguments.Single()) }; + parameters = new() { new DefaultParameter(type2, string.Empty) }; + return true; + } + return false; + case ArrayCreateResolveResult { Type: ArrayType { ElementType: var type3 }, SizeArguments: [{ ConstantValue: int arrayLength }] }: + elementType = type3; + arguments = new(((ArrayCreateExpression)paramsArgument.Expression).Initializer.Elements.Select(e => new TranslatedExpression(e))); + parameters = new List(arrayLength); + for (int i = 0; i < arrayLength; i++) + { + parameters.Add(new DefaultParameter(type3, string.Empty)); + if (arguments.Count <= i) + arguments.Add(new TranslatedExpression(expressionBuilder.GetDefaultValueExpression(type3).WithoutILInstruction())); + } + return true; + default: + return false; } - return false; } } From 4aa777ccc2fa11bc322f505fc8813417e900b0e7 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 22 Jun 2025 08:38:28 +0200 Subject: [PATCH 11/14] Add IsArrayInterfaceType extension method --- .../CSharp/Resolver/TypeInference.cs | 21 ++----------------- .../TypeSystem/TypeSystemExtensions.cs | 17 +++++++++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs b/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs index 1c02d9da6..f09f0ac94 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs @@ -759,7 +759,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver MakeLowerBoundInference(arrU.ElementType, arrV.ElementType); return; } - else if (arrU != null && IsGenericInterfaceImplementedByArray(pV) && arrU.Dimensions == 1) + else if (arrU != null && pV.IsArrayInterfaceType() && arrU.Dimensions == 1) { MakeLowerBoundInference(arrU.ElementType, pV.GetTypeArgument(0)); return; @@ -829,23 +829,6 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return; } } - - static bool IsGenericInterfaceImplementedByArray(ParameterizedType rt) - { - if (rt == null || rt.TypeParameterCount != 1) - return false; - switch (rt.GetDefinition()?.KnownTypeCode) - { - case KnownTypeCode.IEnumerableOfT: - case KnownTypeCode.ICollectionOfT: - case KnownTypeCode.IListOfT: - case KnownTypeCode.IReadOnlyCollectionOfT: - case KnownTypeCode.IReadOnlyListOfT: - return true; - default: - return false; - } - } #endregion #region MakeUpperBoundInference (§7.5.2.10) @@ -880,7 +863,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver MakeUpperBoundInference(arrU.ElementType, arrV.ElementType); return; } - else if (arrV != null && IsGenericInterfaceImplementedByArray(pU) && arrV.Dimensions == 1) + else if (arrV != null && pU.IsArrayInterfaceType() && arrV.Dimensions == 1) { MakeUpperBoundInference(pU.GetTypeArgument(0), arrV.ElementType); return; diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs index 6b9dcb352..fd72e6259 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs @@ -306,6 +306,23 @@ namespace ICSharpCode.Decompiler.TypeSystem } } + public static bool IsArrayInterfaceType(this IType type) + { + if (type == null || type.TypeParameterCount != 1) + return false; + switch (type.GetDefinition()?.KnownTypeCode) + { + case KnownTypeCode.IEnumerableOfT: + case KnownTypeCode.ICollectionOfT: + case KnownTypeCode.IListOfT: + case KnownTypeCode.IReadOnlyCollectionOfT: + case KnownTypeCode.IReadOnlyListOfT: + return true; + default: + return false; + } + } + public static bool IsInlineArrayType(this IType type) { if (type.Kind != TypeKind.Struct) From 18f5b0f2a0bfa32aa3a2e58d87488d262a7f80fa Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 22 Jun 2025 08:39:40 +0200 Subject: [PATCH 12/14] Add minimal support for params collections to Overload Resolution. --- .../CSharp/Resolver/OverloadResolution.cs | 129 ++++++++++++++++-- 1 file changed, 121 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs index bfeb1bbd2..afe3c5075 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs @@ -43,7 +43,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver public readonly bool IsExpandedForm; /// - /// Gets the parameter types. In the first step, these are the types without any substition. + /// Gets the parameter types. In the first step, these are the types without any substitution. /// After type inference, substitutions will be performed. /// public readonly IType[] ParameterTypes; @@ -76,6 +76,24 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver /// public Conversion[] ArgumentConversions; + /// + /// Gets the type of the collection that is used for the 'params' parameter (before any substitution!). + /// Otherwise returns SpecialType.UnknownType. + /// + public IType ParamsCollectionType { + get { + if (IsExpandedForm && Parameters.Count > 0) + { + IParameter lastParameter = Parameters[Parameters.Count - 1]; + if (lastParameter.IsParams) + { + return lastParameter.Type; + } + } + return SpecialType.UnknownType; + } + } + public bool IsGenericMethod { get { IMethod method = Member as IMethod; @@ -83,7 +101,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } } - public int ArgumentsPassedToParamsArray { + public int ArgumentsPassedToParams { get { int count = 0; if (IsExpandedForm) @@ -104,7 +122,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver this.Member = member; this.IsExpandedForm = isExpanded; IParameterizedMember memberDefinition = (IParameterizedMember)member.MemberDefinition; - // For specificialized methods, go back to the original parameters: + // For specialized methods, go back to the original parameters: // (without any type parameter substitution, not even class type parameters) // We'll re-substitute them as part of RunTypeInference(). this.Parameters = memberDefinition.Parameters; @@ -286,9 +304,12 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } if (candidate.IsExpandedForm && i == candidate.Parameters.Count - 1) { - ArrayType arrayType = type as ArrayType; - if (arrayType != null && arrayType.Dimensions == 1) + if (type is ArrayType arrayType && arrayType.Dimensions == 1) type = arrayType.ElementType; + else if (type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT) || type.IsKnownType(KnownTypeCode.SpanOfT)) + type = type.TypeArguments[0]; + else if (type.IsArrayInterfaceType()) + type = type.TypeArguments[0]; else return false; // error: cannot unpack params-array. abort considering the expanded form for this candidate } @@ -772,8 +793,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver else if (c1.IsExpandedForm && !c2.IsExpandedForm) return 2; - // prefer the member with less arguments mapped to the params-array - int r = c1.ArgumentsPassedToParamsArray.CompareTo(c2.ArgumentsPassedToParamsArray); + // prefer the member with less arguments mapped to the params-collection + int r = c1.ArgumentsPassedToParams.CompareTo(c2.ArgumentsPassedToParams); if (r < 0) return 1; else if (r > 0) @@ -797,13 +818,105 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return 1; if (lift1 != null && lift2 == null) return 2; + + // prefer by-value parameters over in-parameters + r = BetterParameterPassingChoice(c1, c2); + if (r != 0) + return r; + + if (c1.IsExpandedForm) + { + Debug.Assert(c2.IsExpandedForm); + r = BetterParamsCollectionType(c1.ParamsCollectionType, c2.ParamsCollectionType); + if (r != 0) + return r; + } + } + return 0; + } + + int BetterParamsCollectionType(IType paramsCollectionType1, IType paramsCollectionType2) + { + // see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-13.0/params-collections#better-function-member + bool isSpan1 = paramsCollectionType1.IsKnownType(KnownTypeCode.SpanOfT) || paramsCollectionType1.IsKnownType(KnownTypeCode.ReadOnlySpanOfT); + bool isSpan2 = paramsCollectionType2.IsKnownType(KnownTypeCode.SpanOfT) || paramsCollectionType2.IsKnownType(KnownTypeCode.ReadOnlySpanOfT); + if (!isSpan1 && !isSpan2) + { + bool implicitConversion1to2 = conversions.ImplicitConversion(paramsCollectionType1, paramsCollectionType2).IsValid; + bool implicitConversion2to1 = conversions.ImplicitConversion(paramsCollectionType2, paramsCollectionType1).IsValid; + if (implicitConversion1to2 && !implicitConversion2to1) + return 1; + if (!implicitConversion1to2 && implicitConversion2to1) + return 2; + } + else if (paramsCollectionType1.IsKnownType(KnownTypeCode.ReadOnlySpanOfT) && paramsCollectionType2.IsKnownType(KnownTypeCode.SpanOfT)) + { + if (conversions.IdentityConversion(paramsCollectionType1.TypeArguments[0], paramsCollectionType2.TypeArguments[0])) + return 1; // ReadOnlySpan is better than Span if there exists an identity conversion between the element types } + else if (paramsCollectionType2.IsKnownType(KnownTypeCode.ReadOnlySpanOfT) && paramsCollectionType1.IsKnownType(KnownTypeCode.SpanOfT)) + { + if (conversions.IdentityConversion(paramsCollectionType2.TypeArguments[0], paramsCollectionType1.TypeArguments[0])) + return 2; // ReadOnlySpan is better than Span if there exists an identity conversion between the element types + } + else if (isSpan1 && IsArrayOrArrayInterfaceType(paramsCollectionType2, out var elementType2)) + { + if (conversions.IdentityConversion(paramsCollectionType1.TypeArguments[0], elementType2)) + return 1; // Span is better than an array type if there exists an identity conversion between the element types + } + else if (isSpan2 && IsArrayOrArrayInterfaceType(paramsCollectionType1, out var elementType1)) + { + if (conversions.IdentityConversion(paramsCollectionType2.TypeArguments[0], elementType1)) + return 2; // Span is better than an array type if there exists an identity conversion between the element types + } + return 0; + } + + bool IsArrayOrArrayInterfaceType(IType type, out IType elementType) + { + elementType = null; + if (type is ArrayType arrayType) + { + elementType = arrayType.ElementType; + return true; + } + if (type.IsArrayInterfaceType()) + { + elementType = type.TypeArguments[0]; + return true; + } + return false; + } + + int BetterParameterPassingChoice(Candidate c1, Candidate c2) + { + Debug.Assert(c1.Parameters.Count == c2.Parameters.Count, "c1 and c2 must have the same number of parameters"); + + bool c1IsBetter = false; + bool c2IsBetter = false; + + for (int i = 0; i < c1.Parameters.Count; i++) + { + ReferenceKind refKind1 = c1.Parameters[i].ReferenceKind; + ReferenceKind refKind2 = c2.Parameters[i].ReferenceKind; + + if (refKind1 == ReferenceKind.None && refKind2 == ReferenceKind.In) + c1IsBetter = true; // by-value is better than in + if (refKind1 == ReferenceKind.In && refKind2 == ReferenceKind.None) + c2IsBetter = true; + } + + if (c1IsBetter && !c2IsBetter) + return 1; + if (!c1IsBetter && c2IsBetter) + return 2; + return 0; } int MoreSpecificFormalParameters(Candidate c1, Candidate c2) { - // prefer the member with more formal parmeters (in case both have different number of optional parameters) + // prefer the member with more formal parameters (in case both have different number of optional parameters) int r = c1.Parameters.Count.CompareTo(c2.Parameters.Count); if (r > 0) return 1; From 8834c02eb72bc94985e09c2b5e2803227455952c Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 23 Jun 2025 20:06:55 +0200 Subject: [PATCH 13/14] Improve naming of delegate-typed variables. --- .../TestCases/Pretty/DelegateConstruction.cs | 8 -------- ICSharpCode.Decompiler/IL/ILTypeExtensions.cs | 2 ++ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs index d07f55c79..48d94770c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs @@ -631,21 +631,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction Action action = delegate { list.Select((int x) => x * 2); }; -#if OPT && ROSLYN - Action obj = delegate { -#else Action action2 = delegate { -#endif list.Select((int x) => x * 2); }; Console.WriteLine(); action(); Console.WriteLine(); -#if OPT && ROSLYN - obj(); -#else action2(); -#endif } catch (Exception) { diff --git a/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs b/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs index e67075aa3..fda8cb533 100644 --- a/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs +++ b/ICSharpCode.Decompiler/IL/ILTypeExtensions.cs @@ -238,6 +238,8 @@ namespace ICSharpCode.Decompiler.IL default: return SpecialType.UnknownType; } + case ILFunction func when func.DelegateType != null: + return func.DelegateType; default: return SpecialType.UnknownType; } From 2fb90b49e0e03cf45fd069438a94373d4a64d782 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 23 Jun 2025 21:15:08 +0200 Subject: [PATCH 14/14] Adjust string.Concat handling in ReplaceMethodCallsWithOperators to support unexpanded string.Concat calls. --- .../Transforms/ReplaceMethodCallsWithOperators.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs index 36624535b..287d7e3f3 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs @@ -57,8 +57,18 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms var arguments = invocationExpression.Arguments.ToArray(); // Reduce "String.Concat(a, b)" to "a + b" - if (IsStringConcat(method) && context.Settings.StringConcat && CheckArgumentsForStringConcat(arguments)) + if (IsStringConcat(method) && context.Settings.StringConcat) { + if (arguments is [ArrayCreateExpression ace] && method.Parameters is [{ Type: ArrayType }]) + { + arguments = ace.Initializer.Elements.ToArray(); + } + + if (!CheckArgumentsForStringConcat(arguments)) + { + return; + } + bool isInExpressionTree = invocationExpression.Ancestors.OfType().Any( lambda => lambda.Annotation()?.Kind == IL.ILFunctionKind.ExpressionTree); Expression arg0 = arguments[0].Detach();